GLENTOP
Programming in C on the AMSTRAD
GLENTOP
PUBLISHERS □ LIMITED
Programming
in
c
on the
AMSTRAD
Ian Sinclair
JANUARY 1986
All programs in this book have been written expressly to illustrate specific teaching points.
They are not warranted as being suitable for any particular application. Every care has
been taken in the writing and presentation of this book but no responsibility is assumed by
the author or publishers for any errors or omissions contained herein.
COPYRIGHT © Ian Sinclair 1986
World rights reserved.
No part of this publication may be copied, transmitted or stored in a retrieval system or
reproduced in any way including but not limited to photography, photocopy, magnetic or
other recording means, without prior permission from the publishers, with the exception of
material entered and executed on a computer system for the reader’s own use.
ISBN 0 907792 86 3
Published by:
Glentop Publishers Ltd
Standfast House
Bath Place
High Street
Barnet
Herts EN5 5XE
Tel: 01-441-4130
(iv)
Contents
Preface
Chapter 1: Introduction to ‘C’
Why ‘C’P • The order of things • Program structure • Source code and
Machine code • HiSoft C summarised
Chapter 2: Starting ‘C’ programs
First steps • Output to screen • Using constants • Constants • More
variables • Getting a value • Other variable types
Chapter 3: Functions and the Library
Functions • The Library • Planning • Some operations • More
planning • More functions
Chapter 4: Pointers
Arrays • Strings at last • Arrays of strings
Chapter 5: Menus, choices and Hies
Other cases • Recording data • String files
Chapter 6: More structured types
Filing structures • Reading back structures • Working with structure files
• Sorting a file • Record nests and choices
Chapter 7: More about pointers
Arrays of pointers • Pointers to functions • Linked lists
(v)
Chapter 8: String functions
Left, right and middle • Concatenation and insertion • Other string and
character functions
Chapter 9: Graphics, sound and BASIC conversion
Sounds unlimited • Using envelopes
Chapter 10: Assortment
Hints & tips • Other actions • More complications • The § commands •
Statics in functions • Binary and machine code • Inserting machine code
Appendix A: Binary, Octal and Hex Codes
Index
(vi)
Preface
Though ‘C’ has been used for a considerable time as a language for writing compilers,
word-processors and games, it has never become as well known as it ought to be, and has
certainly never displaced BASIC as a language for microcomputers. This has been due to
several reasons, some of which are no longer valid. One reason has been that BASIC,
because it allowed direct keyboard commands, was an easier language to learn. This is
certainly true, but it is also true that it is easier to learn to program badly in BASIC, even in
Locomotive BASIC, than to program well. Another restriction was that many ‘C’
compilers did not produce machine code which could then be recorded and used on any
suitable machine. It was common to find that ‘C’ programs could be run only on machines
which were equipped with a complete ‘C’ compiler installed. The most vexing problem was
that ‘C’ was simply not available for machines with small memory sizes. The result was
that this useful, fascinating, though sometimes infuriating language was confined to
programmers who had mini rather than micro-computers.
The arrival of HiSoft C has changed the outlook completely. The version around which
this book is written has been designed for the Amstrad machines, from the CPC464
upwards, including the CPC664 and CPC6128. This version of ‘C’ supports direct
commands which, though not as flexible and simple as those of BASIC, do at least allow to
escape some of the frustration of using other versions of ‘C’. A forthcoming version of‘C’
which runs under CP/M should be even more useful. Now, using HiSoft C, the
programmer can operate with a high level compiled language which includes instructions
for the high resolution graphics and sound, and which compiles to very fast-running
machine code. The serious programmer who uses discs can also produce ‘C’ programs in
machine code which will run on Amstrad machines that are not equipped with HiSoft
C. This language, then, provides all that is lacking in BASIC, and much more.
I have assumed that the reader of this book will have programmed in BASIC. Since the
book is written for the Amstrad user, this seems to be a reasonable assumption, and it
provides a basis of comparison between Locomotive BASIC and ‘C’. In addition, I have
assumed that the reader who wishes to use graphics and sound is familiar with the methods
of Locomotive BASIC. This is important, because in HiSoft C, the graphics routines are
rather similar, and the simpler types of sound instructions should be useable by anyone
who has used the sound system in BASIC. HiSoft C, however, offers significant extensions
to these commands. These extensions form part of the HiSoft Library of‘C’ functions, and
very many of these functions have been more fully described in this book. In addition, some
of the non-standard features of HiSoft C, such as the inline statement, have been
explained, with examples, in a more complete way that has been provided in the manual.
(vii)
In some ways, it is easier to learn a new computing language if you have previously learned
any other computing language. This is not always true when the first language is BASIC,
however, because ‘C’ offers much that can only be accomplished with a lot of tortuous
effort in BASIC. Very often, a BASIC programmer is tied to BASIC methods, and cannot
see that there are simpler ways open to him/her when using another language. I have taken
every opportunity to point out the new ways in this book. I have also included many
reminders about old BASIC habits that must be abandoned when you learn to program in
‘C\
Because the way that you write ‘C’ programs is very closely tied to the way that you design a
program, I have linked these topics together rather than trying to deal with the design of
programs separately. The book has been written entirely around the conventional ‘top-
down’ programming method, and the programming has been approached in the same way.
The system which was used was a CPC464 with a disc drive, but the book is equally
suitable for the user who has bought HiSoft C on cassette. Obviously, the book will also be
suitable for the buyers of the CPC6128 model which are now available. Throughout the
book, the carriage-retum/newline key has been referred to as (RETURN) (as on the
CPC6128) or as (ENTER) (as on the CPC464). Where I have used commands that are
peculiar to the disc system, I have pointed this out. My HiSoft C disc was a stock item,
purchased by mail, and not a pre-production sample. The version number was VI.2. The
reader should find, then, that there are no significant differences between the ‘C’ of my
descriptions and the ‘C’ that he/she has bought. The programs that appear in this book
have been photo-copied from listings which were produced by an EPSON printer reading
the ‘C’ program direct from disc. The listings in the book are therefore exactly of the
programs which I used. No line numbers are shown, because the line numbers that appear
on the screen are not sent to the printer.
Finally, I am very grateful to HiSoft for this excellent version of‘C’, which has breathed life
into the learning of this fascinating language. I am indebted to them for permission to print
a few of the large number of library functions which form part of the HiSoft C package for
the Amstrad. In particular, I want to thank Dave Howarth for many interesting hints. I am
also most grateful to Dr. Peter Holmes, of Glentop Publishers, who warmed to the
prospects of ‘C’, and commissioned this book.
Ian R. Sinclair
September 1985
(viii)
Chapter 1
Introduction to ‘C’.
Why ‘C’?
If you program in BASIC and find the language perfectly adequate, it’s reasonable to ask
“why use ‘C’ ?”. Certainly, if you find the Locomotive BASIC of the Amstrad completely
suited for what you want to do, you probably don’t need ‘C’. On the other hand, it’s much
more likely that you have found a lot of items for which BASIC, even the excellent
Locomotive BASIC, is not well suited. One obvious set of such items on every version of
BASIC is the programming of database records. Others are less obvious, unless you are
trying to write business software that requires the use of lists, or games software which uses
sets of items. BASIC can cope with these needs only in roundabout ways, and if you have
only ever programmed in BASIC, you simply don’t realise how much effort you are having
to make for what are quite simple actions in other languages.
Another reason for using ‘C’, of course, is speed. BASIC is an interpreted language. This
means that every instruction in a BASIC program is carried out as the machine comes to it.
A computer carries out these instructions by converting them into machine code and
running the machine code. An interpreter takes the instructions one by one, finds the
machine code for an instruction, runs that instruction, then looks for the next one. By
contrast, a compiler deals with the whole of a program and converts it into one piece of
machine code. This code can then be recorded and run. The advantage of this is that all of
the looking-up and converting steps are done once, in the action which is called
compiling, and this compiled code can run fast, usually almost as fast as machine code
which has been written using an Assembler. When you use an interpreted language, the
interpretation has to be done for each step and for each time the program is run. For
example, suppose that your program consists of a loop which prints a string variable value
fifty times. An interpreter would look for the machine code for the print operation, find the
address and length of the string of characters, and carry out the same action fifty times. All
of the looking up is repeated on each pass through the loop. A compiler, by contrast,
would generate machine code which carried out the print action in a loop. This generation
would be done once, during compiling, and because the looking up actions do not have to be
done again, the result is very much faster. The speed of a compiled language may be
important for you if you want to write either business or games software, though the
advantage is less for some types of games programs. The reason is that the ‘C’ compiler
makes use of the built-in screen routines of the Amstrad machines, which are comparatively
slow. HiSoft C, however, allows you to mix in machine code with your ‘C’ so as to carry out
actions like direct access to the screen memory.
1-1
Yet another reason for using ‘C’ is that the language is much closer to the languages that are
used on larger machines. It is also ‘portable’ in the sense that a program which is written in
‘C’ for one machine will probably work with another machine. This assumes only that
there are no instructions which are peculiar to one machine, and ‘C’ is particularly good in
this respect. The way that ‘C’ is designed, there are a few instructions that you find in all
versions of‘C’, and a lot of actions (the library functions) that you can call up to make any
particular machine go through its tricks. Above all, though, ‘C’ is a language that forces you
to think carefully about what you want a program to do. It is possible to write poorly-
designed and sloppy programs in ‘C’. It is also possible to write neat and tidy programs in
BASIC. In the normal run of events, however, both of these are unusual. One of the things
that endears ‘C’ to old BASIC programmers is that ‘C’ is a flexible language. You aren’t tied
to the very rigid rules that you find, for example, in Pascal. On the other hand, you pay for
this flexibility in being allowed (some might say encouraged) to make mistakes. The ‘C’
compiler won’t stop you when you try to do something silly, and it can sometimes be very
difficult to find out just what is causing the trouble.
The order of things.
‘C’ is not simply a compiled language, it is a language in which programs can be written
which compile to unusually compact and fast machine code. This is possible only if you
program in the correct order, though. The most important idea to get used to, if your
programming experience is in BASIC, is that types of variables and their names have to be
declared before they can be used. In BASIC, you can write a line like:
100 A%=5
which introduces a variable name, A%, with the % sign meaning that this is an integer. At
the same time, the value to which A% is assigned is made equal to 5. A ‘C’ programmer can
write a very similar kind of line, but it has to appear early in a program, before the variable
will be used. The ‘C’ form of this line will be:
static int a=5;
with int used instead of the % sign to mean that a represents an integer. The word static
refers to the way that the value is stored, and will be explained later. Using this
declaration means that we use only a in the program, not int a or a%. The use of the
equality sign and the 5 then assigns a value of 5 to a. The assignment does not have to be
made here, it can be done later in the program. Declaring a variable type, and assigning a
value in one step is just one of the short cuts that ‘C’ allows and which makes it such a very
interesting and challenging language.
This idea of declaring what type of variable is represented is not one that appears much in
BASIC. The Amstrad machines, along with a few machines which use Microsoft BASIC
(such as the MSX machines) use instructions such as DEFINT. This allows you to define
how letters will be used in the program. For example, DEFINT A-D in Microsoft BASIC
means that any variable name which starts with the letters A, B, C, or D will be an integer
variable. This means that you no longer have to mark integer variables, like A%, BY%,
COWS% and so on, to show that they are integers. The DEFINT statement at the start of
the program has done this for you, and it’s also possible to define string or real-number
variables in a similar way.
1-2
In ‘C’, however, this idea of declaring how names will be used in advance is all-important,
though different in style. Note, too, that I said ‘names’. In Locomotive BASIC, you are
probably used to working with variable ‘names’ like Apple, Belong! and so on. ‘C’ also
allows you to use realistic names rather then just letters singly or in pairs. This is an
advantage in ‘C’, because using long names does not slow down the action of the program as
it does with BASIC. You must, however, define what type of item each name will be used
for. This allows the compiler to prepare for each variable that will be used by making the
correct allocation of memory. You cannot write statements which in BASIC look like:
NAME=“SINCLAIR”
in ‘C’ unless you have, earlier in the program, declared that NAME is a variable that will be
used for a string of at least nine characters. Nine has to be used for an eight-letter name,
because in ‘C’, a string must end with a zero, which is the ninth character. You can’t make
this declaration later, because the compiler will halt when it finds a name used that it has
no notification about. You cannot ever use a name unless you have defined the name. The
definition does not necessarily need to be placed at the start of the program, but it certainly
must come before you attempt to use the name. So that I don’t have to use long-winded
phrases each time I remind you of this idea, I’ll give it its correct name from now on- it’s
called pre-definition.
Program structure.
The structure of a program means how it is arranged and organised. Some BASIC
programs are about as structured as the path of a drunken fly. Others are neatly arranged
with a simple main core program which calls subroutines to perform the actions. If you
have written programs of the core-and-subroutine type, then it’s likely that you’ll take well
to ‘C’. If your programs have been of the ‘fly-track’ variety, you will have real problems! ‘C’
forces you to have some structure about your programs, and the type of structure is one
that is far removed from BASIC. For example, in some varieties of BASIC, you might call
up a subroutine in a line like:
220 GOSUB 5000:PRINT A;
which carries out a subroutine, prints the value of a number, and keeps the printing in the
same line.
This could never be mistaken for a ‘C’ line. For one thing, ‘C’ lines aren’t normally
numbered, although HiSoft C for the Amstrad uses line numbering for your convenience in
editing. If you write the lines in the correct order, the order that the compiler will deal with
them, there’s no need for line numbers. The second point is that the subroutine starts
somewhere later in the program, at line 5000. We can’t have such a thing in ‘C’, because the
compiler can’t use line numbers. Instead of using a subroutine which is called by its line
number, ‘C’ uses a function which is called by using its name. Users of the BBC Micro
(and the QL) are familiar with this idea, and the principle is developed further in ‘C’. Even
the instruction PRINT is not used in ‘C’, and the semicolon does not mean ‘don’t take a new
line’. Did I really say that knowing one computer language would prepare you for another?
1-3
The point that is really important here, though, is order. In BASIC, you can write a core
program which has calls to subroutines. It won’t run correctly until the subroutines have
been entered, but you can place the subroutines anywhere you like in the program. By
contrast, defined functions in Locomotive BASIC must be placed ahead of the point at
which they are called. If you have a function which, for example, multiplies a price by 0.15
so as to work out the amount of VAT, you must, in BASIC, define it before you use it. You
would have lines in BASIC such as:
10 DEF FNVat(S)=S*.15
20 INPUT “BASE PRICE”;X
30 PRINT“VAT IS ”;FNVat(X)
because the BASIC interpreter can’t look ahead for FNVat. The similarity here is that a
defined function in Locomotive BASIC is called into action by using its name, FNVat in
this example, rather than by using a line number. In a ‘C’ program all functions must be
defined, but this can be done following the main program. Like a well-structured BASIC
program then, which might consist of a core program of perhaps ten to a hundred lines of
main program followed by subroutines, a ‘C’ program is written in the order of main, then
functions. This means that details, such as declaring variable types, all come at the start of
the program, followed by the main program, and the functions often come last of all. This is
a very logical arrangment as far as the programmer is concerned. It cartainly makes the
writing of programs much simpler than is the case in other languages. Any modem version
of‘C’ can be expected to possess a good editing system, so it’s easy to add statements at the
start or at the end of a program if you have left something out. If you have programmed for a
long time in BASIC, you might feel rather lost without line numbers, and HiSoft C
therefore allows you the use of line numbers. This is shown only in the editing example in
the manual, but you can use line numbers just as you do in BASIC, and the line numbering
can be made automatic in the same way as used in Locomotive BASIC. For example,
typing 110,10 (ENTER) will cause the line numbering to start at 10 and to change in steps
of 10 .
The line numbering is not essential in HiSoft C. With the compiler switched in, you can
enter a set of statements with no line numbers, and you can then compile and ran the
program. You cannot, however, record the program to use again. The line numbers are also
very useful if you want to list selectively, or delete some lines. You can use a line-editing
action which is similar to that of the Amstrad machine. The line numbers, however, are not
recorded along with the program, and you can find that your program has an entirely
different set of line numbers when you replay it. In addition, the line numbers are not
printed when you take hard-copy. The examples in this book have been printed directly
from the text of working progams, with no line numbering. You can, if you like, prepare
your programs using a word-processor, leaving out line numbers, and recording on tape or
disc. If you program in BASIC in this way, you will know what is involved. You can also
read recorded ‘C’ programs with a word-processor, or by using the CP/M TYPE
command.
1-4
The use of line numbering just like BASIC makes it much easier for you to have second
thoughts. It’s easy, for example, to insert new lines just by giving them suitable numbers.
You can, for example, put in lines with numbers 11,13,14,16 and so on, between line 10
and line 20. If you find that you are running out of numbers, then HiSoft C comes to your
rescue with the facilities that you normally have on the Amstrad machines. You can, for
example, renumber lines with a higher increment number. If you find that you are
writing so many extra lines that you will need to use many more lines, then you can
renumber all the existing lines of the program by typing simply n then (ENTER). This will
renumber so that the first line number is 10, and the subsequent lines are numbered in tens.
You can then continue to insert lines with intermediate numbers, and renumber as often as
you like. If you have programmed in BASIC using the popular plan of reserving certain
ranges of line numbers for certain tasks (like 10 - 100 for the main program, and each
subroutine starting at 1000,1200,1400 and so on), then forget it! This is never needed in ‘C’,
and in any case, a program is always automatically renumbered when it is replayed from
tape or disc. You can also delete lines in a similar way. Typing dl00,200 will delete all lines
between, and including, 100 and 200. A command such as dlO will delete a single line.
Unlike BASIC, typing a line number and then pressing (ENTER) will not delete a line. It
will only bring up the polite message ‘Pardon?’, which is how the HiSoft Editor for ‘C’ deals
with anything it can’t recognise. The command dl,32767 will delete all the lines of a
program.
In addition to these useful editing commands which are a normal part of the BASIC of the
Amstrad, HiSoft C allows you a couple of commands which are more commonly used in
word-processing programs. One is f, which is used to find any string, such as a name. You
have to specify the range of line numbers over which you want to search. The command
fl0,1000,copy (ENTER) will list on your screen (or printer) each line which contains this
word copy. The line appears on the screen, ready to edit using the arrow and (DEL) keys in
the usual way. The range of line numbers that is needed has to be entered only once if you
are using the same range for several searches. After the first search, you can use f„newone
for example, omitting the line numbers. The other action of this type is the change action.
This is used to change one string into another. For example, fl0,1000,copy,turn
(ENTER) will list all the lines that contain copy and allow you the choice of altering each
copy to turn if you want to. If you want to make the change, you press CTRL s, and you’ll
see the alteration carried out, then the next word will be found.
Source-code and Machine-code.
HiSoft C is sold in two versions, cassette and disc, which operate in almost identical ways.
Normally, you will work with the compiler program held in the memory of the Amstrad for
all the time that you need to use it. When you type a program, that program exists only as a
set of ASCII codes, just like the output from a word-processor. This is called the ‘source-
code’, and is recorded on cassette or disc by using the p command, more of which will come
later. When a program like this is loaded from the disc or cassette, it is still just a set of
ASCII codes. To make the program run, it first has to be compiled, and then set into action.
1-5
This can take some time, particularly if the program is a long one. The reversi.c which is
provided with HiSoft C is a good example of another problem that can arise - it’s too long to
compile in this way on the CPC464 machine! Another disadvantage, however, is that only
an Amstrad which has HiSoft C loaded into it can load, compile and run a program which
has been created this way. This is because normal compiling action does not record or
replay machine code. All that is recorded is a serial file of strings, the instructions of the ‘C’
program. These have to be read and compiled before running is possible.
There is an alternative however. By making the first instruction of a long program (or any
other program you want to use) ^translate filename, using your own filename, you can
compile and record machine code. If the program is a very long one, you record the source-
code, including the #translate instruction. You then compile the recorded program, using
the instruction ^include source_filename. This loads the compiler into the memory
only when it is needed. The program can be read from disc or cassette, compiled, and the
resulting machine code stored back on the disc or cassette. This allows you to ran
a ‘C’program without having HiSoft C in your machine, and in this way much longer
‘C’ programs can be compiled. Once this has been done,the code can be read from the
disc by any Amstrad machine which has enough memory , and ran. In this way , a
program which you have written in ‘C’ can be used by any Amstrad, even one which is
not supplied with HiSoft C on cassette or on disc.
Throughout this book, the examples will be useable in any form you please. As with any
compiled language, the main advantages of using ‘C’ are available only to disc users, and I
believe that most programmers in ‘C’ will be using a disc system, as I am. Nevertheless, if
you are learning about HiSoft C with only a cassette system, you will be able to use all of the
examples which are included here, though progress will be slow. The programs that are
illustrated all use lower-case letters for words of command. You must use lower-case
letters for program instructions and for commands to the compiler, because upper-case
letters will not be accepted. The use of upper-case letters gives you a set of ‘undefined
symbol’ error messages. Because of this, all references to program instructions in the text
will be in bold type. The w command will list a ‘C’ program on your printer if you have a
printer connected, and the listings that are reproduced in this book have all been made by
using the w command to an EPSON RX-80 printer.
HiSoft C summarised.
Unless you have used other varieties of‘C’, the advantages of HiSoft C are not necessarily
clear to you, even after reading the manual. You can also find that, unless you are a fairly
experienced ‘C’ programmer, even ‘C’ programs printed in other books may not run when
you try them, because you need to know how the Amstrad version works first. The main
point is that HiSoft C is a version which corresponds very closely to international
standards. You should never have to re-leam your ‘C’, because there are not the 57
varieties of‘C’ that you find with BASIC. If you change your computer for a later design,
then you will find that you can use ‘C’ on the new machine as readily as you did on the old.
This is particularly true if HiSoft C is also available for your new machine.
1-6
Having praised the advantages of standardisation, however, there are also some non¬
standard advantages. The Amstrad is a machine which has quite superb capabilities for
graphics and sound, better than those of many other machines. HiSoft C comes with a
library of functions to allow you to control these features of the Amstrads, and obviously
these features would not necessarily be available on other computers. There are also
provisions for writing in assembler language, and for direct commands.
Command
Effect
1 a
Set drive to A.
1 b
Set drive to B. Will cause a lockup if no ‘B’ drive exists.
1 cpm
Switch to CPM, clearing out ‘C\
1 dir
Display disc directory.
1 dir file
Display filename, which can use wild card.
1 disc
Switch to disc in, disc out.
1 disc.in
Switch to disc in.
1 disc.out
Switch to disc out.
1 drive letter
Switch to drive of specified letter, if present.
1 era name
Erase file of specified name. A wildcard can be used.
1 ren old new
Rename old filename as new.
1 tape
Switch to tape in, tape out.
1 tape.in
Switch to tape in.
1 tape.out
Switch to tape out.
1 user number
Change user number.
Owners of the CPC464 should note that these bar commands use the neater style (no
‘@’strings needed) of command as used by CP/M and also used in the 664 and 6128
machines.
The bar commands which can be used direct from the keyboard during editing.
FIGURE 1.1
1-7
The bar( I ) commands that are listed in Figure 1.1 can also be used directly from the
keyboard. I dir, for example, is very useful to find what is on your disc at any time before
you start saving a program. You also have access to all the rename and erase commands for
disc operation without having to go back to BASIC. You can also set the programmable
keys for some of the most-used ‘C’ instructions, saving wear on your typing fmger(s).
The use of line numbers when entering ‘C’ statements makes editing particularly easy,
especially when you can make full use of the familiar editing system of your Amstrad. This
makes learning HiSoft C very much easier, because you do not have to struggle with an
unfamiliar editing system at a time when you are likely to make a large number of mistakes.
For disc users who choose to use the Disc version, there are many advantages in HiSoft C.
Of these, the main advantage is that the library is much easier to use. This is another
feature of ‘C’ which is difficult to grasp if you have only programmed in BASIC. Every
version of‘C’ comes with the small set of‘C’ instructions built in, but also with a ‘library’.
The library is a set of recorded functions. It’s rather like getting a version of BASIC which
came with a disc full of ready-made subroutines. These library functions contain a lot of
extensions to the language, not least of all the special instructions that are needed for sound
and graphics on the Amstrad machines. When you have to use cassettes, getting these
library functions off the cassette and into memory takes rather more time than most of us
can spare. When the library is held on disc, the need to wait is much reduced. In addition,
when you make use of discs, the full range of disc filing commands can be used in programs.
1-8
Chapter 2
Starting ‘C* programs.
Loading in.
The ‘C’ disc or cassette is loaded into your Amstrad in much the same way as any other disc
which contains machine code. This means that the ‘C’ compiler is recorded in the form of
an unprotected BIN file, which is loaded and run by using RUN“hisoft-c” (ENTER). Once
this has been done and the compiler is in action, your only way out is back to BASIC, by
pressing ESC SHIFT CTRL together. This will re-start BASIC, and remove all of the
compiler code. The disc contains several other sections of code, including the library
routines, which are arranged to load in as and when necessary. These other sections are
held as ‘C’ source files, which means that they are in ASCII code, and can be read by the
CP/M TYPE command or the g„filename command of the ‘C’ editor. To see these files
using CP/M, and to print them out if you have a printer, start by switching back to BASIC.
Insert the CP/M master disc, and then type I CPM (ENTER) to get into CP/M. Now
remove the CP/M disc and insert your HiSoft C disc. To see the file extcmd.h, for
example, type type extcmd.h and press (ENTER). You will hear the disc spin, and see the
file appear on the screen. If you have a printer connected, then using CTRL P before
entering the TYPE command will cause the file to appear on paper. The alternative is to
start ‘C’ running, use the command dl,32767 to make sure that the memory is clear, then
use g„filename to get the file. The file can be printed out using the w command. You can
then take a look at a professional class of ‘C’ program. Don’t let it put you off!
You have paid for the master copy of‘C’ on cassette or on disc, and there is no reason why
you should not make backup copies of the disc. The HiSoft manual shows how to make
copies of the library routines from the cassette, and making disc copies of the main program
and the library routines is just like copying the CP/M master disc. Certainly if you intend to
be using ‘C’ for many years, it would be preferable to have at least one backup for the disc.
Your backup should contain all of the files except reversi.c, and you can leave the backup
disc unprotected, so that you can add your own ‘C’ files to the list. It’s a good idea to have
the HiSoft C and the library files on each disc that you use, to save having to use the master
copy. If you use cassettes, then you have to swap the cassettes around a lot in any case.
Remember that once you have entered ‘C’, you cannot use familiar commands like cat or
list. You can use the bar (I ) commands in the usual way, however, with the usual
reminder that if you use I CPM, the CP/M master disc must be in the drive, and you will
then lose the ‘C’ compiler. You can, however, use commands like I ren and ! era in their
CP/M form.
2-1
First steps.
All ‘C’ programs can be constructed in the same way, and though you can leave out some
steps in HiSoft C, it’s advisable to keep to the rules of standard ‘C’. If you do, it’s much
easier to write ‘C’ programs for practically any machine. In addition, it helps a lot if you
write programs the way that you ought to design them, outline first and details later.
Remember also that you need to keep to Amstrad rules as well. For example, you must, if
you are not using the automatic line numbering, remember to leave a space following a line
number. Failing to do this will make your ‘C’ programs look very peculiar. The simplest
possible outline of a ‘C’ program then, is:
10 main()
20 !
30 /‘statements;*/
40 }.
- and we now have to take a look at this to decide what is important in these few lines. One
thing which is very important is the way that the semicolon is used. In BASIC, the
semicolon is used to ensure that printing is to be kept on the same line. In ‘C’, the semicolon
is used as a separator, showing the end of a statement. This is the sort of thing you do in
BASIC just by ending a line and taking a new line number. In ‘C’ you can, for example, use
the semicolon to separate statements in the same line, as you use the colon in BASIC.
Omitting the semicolon is a way of instructing the compiler that there is more of a
statement to come. At this stage, it’s a bit pointless to describe the rules, because until you
have had some experience in writing programs, you won’t really see why semicolons are
used in some places and not in others. For that reason, I’ll point out in each of the early
examples the few instances in which a semicolon has not been used where you might expect
it.
In the example of program outline, the first line consists of the special name main(). A ‘C’
program consists of a set of named functions, using whatever names you like to give them,
but there must always be one that is called main. The brackets are an essential part of this,
and it’s not very often that you need to put anything between these brackets. For other
names of functions, though, you will want to place various items between the brackets. In
any case, you can’t omit them. The main program is the one that calls up all of the other
functions, just as a BASIC core program can call up various subroutines. The curly
brackets then show the start and the end of the main program. The {indicates the start,
and the j shows the end. You don’t need any other way of marking the end of the program.
The simplest possible program is then written using keywords. A keyword in ‘C’ is rather
like a keyword in BASIC, it is reserved for a special purpose and you can’t use it for
anything else. Keywords must be correctly spelled, otherwise an ‘Undefined symbol’ error
will be announced after you have compiled and when you try to run the program. This
means that any of the errors which you would think of as ‘syntax errors’ in BASIC are very
often not discovered in a ‘C’ program until after compiling. This wastes a lot of time, so you
need to be rather careful about checking what you type. It isn’t made easier by the difficulty
in reading the Mode 2 lower-case printing on the Amstrad screen, either. Figure 2.1 shows
the keywords of HiSoft C.
2-2
auto
Specifies type of variable or function.
break
Break out of loop.
case
Marks a choice made by using switch.
cast
Change the type of a variable.
char
Character variable type.
continue
Go to start of loop.
default
Select default option in switch.
do
Start of do..while loop.
double
Double precision variable, not implemented in V.1.2.
else
Alternative in if statement.
entry
Not implemented.
extern
Used in V.1.2 to declare non-integer functions in advance.
float
Floating-point variable, not implemented in VI.2.
for
Start of counter controlled loop.
goto
Jump to position of label name.
if
Test word, used along with else.
inline
Used in VI.2 to head list of machine code bytes.
int
Integer variable type, range -32768 to +32767.
long
Double size variable type, not implemented in VI.2.
register
Variable type, not implemented in VI.2.
return
Pass back value of parameter from function.
short
Normal variable type, only type used in VI.2.
sizeof
Measuring number of bytes in variable.
static
Type of variable using fixed memory.
2-3
struct
switch
typedef
union
unsigned
while
Compound variable consisting of several fields.
Passes control to one of a number of statements.
Defines a name as meaning a variable type.
Variable type which can be one of a defined group.
Number in range 0 to 65536.
Marks start of while loop, or end of do loop.
The keywords of HiSoft C. Some, such as long and float, are not implemented in the first version.
FIGURE 2.1
Note that most of these, apart from cast and inline will be found on other versions of‘C’,
but other versions, notably the ‘C’ for the IBM PC, have a few more keywords, in particular
for random access disc filing.
The word main, note,is not one of the keywords. This doesn’t mean that you can use it for
anything you like, but it is a title for the main program, not a word which describes an
action. This is an example of a word which is an identifier. In BASIC, the only identifiers
that you use are filenames and names for variables. ‘C’ uses a lot more types of identifiers,
and they are used in much more interesting ways. In this case, the word ‘main’ identifies the
main program, and you could use other words to identify the functions (the ‘C’ replacement
for subroutines) which are called up by the main program. You also use identifiers for other
things, like variable names, subject to a few rules. The rules are that an identifier must start
with a letter, with upper-case and lower-case being treated as identical. Since you must use
lower-case for keywords, it makes sense to stick with lower-case for identifiers too.
Professional programmers use upper-case letters in indentifiers which are present for
special purposes, as we’ll see later. You can then follow this letter with other letters or with
digits, but no blanks or punctuation marks. The only character that is allowed, apart from
letters and digits, is the underscore (_), which you get by pressing the SHIFT 0 key. The
underscore is useful as a way of making long names more readable (like name_of_item).
You could, if you wanted to, start a word with an underscore, but there again its better not
to, because this could lead to trouble later on in your ‘C’ programming career. The reason is
that words which begin with an underscore are used within the compiler, and unless you
can be sure that you are using different words, you can cause problems. You can use names
of more than eight characters, but only the first eight characters will count. This gives
you rather less choice about things like variable names than you have in BASIC, because
Locomotive BASIC places no restriction on name lengths up to 40 characters long.
You probably know that in Amstrad BASIC, there are some variable names that you
cannot use, such as PRINT, TAB and any other reserved word in upper-case letters.
HiSoft C, like other versions, has some identifier names which are already allocated.
2-4
bit
Move bytes in memory.
define
# command.
direct
# command.
error
# command.
fclose
Close file.
fopen
Open file.
fprintf
Print to file.
fscanf
Read from file.
getc
Character from file.
getchar
Character from keyboard.
include
# command.
isalpha
Test for letter.
isdigit
Test for digit.
islower
Test for lower case.
isspace
Test for space.
isupper
Test for upper case.
keyhit
Test for key struck.
list
# command.
main
Marks main program.
printf
Print on screen.
putc
Send character to file.
putchar
Place character on screen.
rawin
Read keyboard for key.
rawout
Send code to screen.
2-5
scanf
Read variable value from keyboard.
sprintf
Send string to file.
sscanf
Read from string into other variables.
swap
Exchange variable values.
tolower
Alter character to lower case.
toupper
Alter character to upper case.
ungetc
Put character back on file.
Identifiers that are already allocated. These belong to functions which are built-in to the HiSoft compiler.
FIGURE 2.2
These are listed in Figure 2.2, and when you look at this list, you might think that these
were another set of reserved words, as many of them would be in BASIC. The difference is
important. All of these identifier names can be used by you for something else if that’s what
you want. If, for example, you want to call your program gets or puts then you can do so.
You would be foolish to do this, because by changing the meaning of these names, you are
losing the use of some action that you might need, but you will not cause any error message.
The difference is important, because if you try to use a reserved word for anything else, the
error will be signalled; if you use one of the ‘predefined’ identifier words, there’s no error,
and you won’t be informed. You may wonder, however, why some action later turns out to
be impossible! The words in Figure 2.2 are the names of the library routines of HiSoft C,
and each of them will call up a routine which may be part of the compiler (an internal
function), or from the library on disc or cassette. This allows you to incorporate ready-
written pieces of ‘C’ into your own programs, saving a lot of reinventing the wheel.
Following the main() identifier, there is a newline (obtained by pressing the (RETURN)
or (ENTER) key) and an opening curly bracket. This must be present, and if you omit it you
will see an error message, usually ‘Bad declaration’, when you try to compile. The opening
curly bracket marks the beginning of any program or piece of program, and follows the
program name and declaration of variables. This is something that we’ll look at very
shortly. The next line is where we would expect the program to do something. Instead, all
that we have is /‘Statements*/. The combination of the forward slash (under the question-
mark) and the asterisk (with no space between them) has the same effect as REM in BASIC.
It marks a piece of the program which is just a reminder to the programmer. Unlike BASIC,
you have to mark both the beginning and the end of the remark. In addition, a reminder in
‘C’ does not slow down the program in the same way as a REM in BASIC does. This is
because the compiler ignores the reminder and no code is generated. In BASIC, a REM still
has to be looked at each time the program runs, just to read the code that means REM. In
this line, I have put a semicolon to remind you that there will be a semicolon following each
statement. Finally, the ‘C’ program ends with the closing curly bracket. The pairs of curly
2-6
brackets can be in many places in a ‘C’ program. This is because each section of a program
has a beginning and an end, and the curly brackets are used to mark them. Any but the
simplest ‘C’ program will be written as a set of named functions that will be called by the
main program, and each of these procedures will have an opening curly bracket and an
ending curly bracket. If, incidentally, you miss out the ending curly bracket in the main
program, you may find that the error is not picked up by the compiler. The program will
compile, but it will not run. In the example, it will stop with the error message ‘expecting a
primary here’. The main reason for this is that it can’t think of any other name for the error!
You can now check the sample program. If you have started by typing il0,10 for automatic
line numbering, you will have to leave this by pressing (ESC) after the last line of the
program. If you need to edit a line, then type e followed by the line number and press
(ENTER). Use the right shift and (DEL) key to repair the damage, and press the (ENTER)
key again when you have finished. Use 1 (letter ell) to get a listing of the program to check it.
Once you are sure that it is perfect, you can compile it. This is done by typing c (ENTER).
This clears the screen, setting 80-character lines again if you had switched to 40 characters
for readability, and just waits. Using c just switches the compiler in, it doesn’t start it. To
start compiling, type ^include, and (ENTER). If you have made no mistakes, you will see
the lines of the program appear in order, followed by the cursor. If you do find errors in this
example, then it’s nearly always going to be omission of a curly bracket or a slashmark. You
then need to prepare for running. The compiler pauses at the end of compiling so as to allow
you to put in special commands to the compiler, but in most cases this isn’t needed. To
indicate that you want to use the program, you press (CTRL Z). This brings up the message
‘Type y to run’, and pressing the ‘y’ key will run the program. If a mistake like a missing
final curly bracket has been found, you’ll get an error message in place of the‘T ype y to run’
message. You can then run the*program by typing y without needing to press (ENTER).
Since the program doesn’t do anything, nothing appears on the screen except another
invitation to run the program by pressing the ‘y’ key. Pressing any other key puts you back
into the hands of the editor.
Output to the screen.
You have probably already noticed that ‘C’ does not have a reserved word print. There is,
in fact, no reserved word for the action of putting something on the screen. This action is
one which carries an identifier name for a library routine rather then being one of the
reserved name actions. The identifier word that you need is printf. Unlike most of the
library routines, printf is built-in as part of the compiler code, so that the routine does not
have to be read from the disc each time you compile it. The use of printf is, however, quite
different from the use of PRINT in BASIC. The name printf has to be followed in brackets
with details of what has to be printed. This means not only what you want to print, but also
how you want it printed, formatting as it’s called. The formatting commands and the items
that you want to print are all included within brackets, with quotes around the formatting
commands and any characters that are to be printed. What is ‘written’ on the screen in this
way can be a number or it can be text. The simplest possible examples of text writing look
sufficiently like BASIC to be easily understood when you are reading a ‘C’ program. Figure
2.3 shows an example, which you can type, compile and run.
2-7
main <)
pri ntf<“\nWords");
printf(“\n%d“,5+3*2)j
A short program in ‘C’ to get you familiar with compiling.
FIGURE 2.3
In this example, the actions are of writing a word and performing a piece of arithmetic. The
writing of a word is rather different to the PRINT “Words” that would be used in BASIC.
The word has to be placed between quotes, and also has to be placed between brackets. The
semicolon at the end of the line has nothing to do with the printing action, remember, it’s
just the signal to the compiler that there is more to come. The real novelty here is the \ n
which appears within the quotes and just ahead of Words. The \ is the backslash sign,
which is on the key next to the right-hand SHIFT key. Don’t mix this up with the forward
slash next to it which is used in /*rem*/ lines. The effect of \ n coming before the text is to
force a newline before anything is printed. You could also place another \ n after the text to
cause a new line to be taken after printing. There is a complete set of these backslash
instructions, all of which must be included between quotes in a printf type of statement.
Figure 2.4 shows this set.
Mark
Meaning
\ n
Newline (ENTER/RETURN key).
\t
Tab (one space default).
\ b
Backspace.
\r
Carriage return (not newline).
\ f
Printer formfeed, screen clear.
V
Put in single quote.
\“
Put in double quote.
Any other codes can be put in as numbers in octal code following the backslash. For
octal codes, see Appendix A.
The specifier letters which can follow the backslash.
FIGURE 2.4
If, for example, you use \ f this will carry out a formfeed if sent to the printer, and will clear
the screen if the screen is being used. In the next line, what is written is still placed between
2-8
brackets, but there are no quotes. The arithmetic result is printed out, just as it would be
by PRINT 5+3*2 in BASIC. As in BASIC, the multiplication is carried out before the
addition, so that the result is 11, not 16. Here again a semicolon has been used to mark the
end of the statement, and there’s another \ n used to cause a newline. The novelty this
time is the %d which follows the \ n. The % sign is a general way of indicating how you
would like a number printed, and when it’s followed by a d, then the number is printed in
denary. If you haven’t come across this term before, it means the ordinary scale-of-ten
numbers that we use. Once again, there is a whole set of these ‘number specifiers’, and
Figure 2.5 shows the complete list. After this line, the main program ends with the curly
bracket.
Mark
Meaning
%d
Signed denary number, range -32768 to +32767.
%u
Unsigned denary number, range 0 to 65535.
%o
Unsigned octal number.
%x
Unsigned hexadecimal number.
%c
Single character.
%s
String ending with a zero.
%%
Print % sign.
Quantities are normally printed right-justified, but using a negative sign before the
specifier letter will force left justification. Each specifier letter can be preceded by a
number to set minimum field size, 0 will print a leading zero or blank.
The formatting code letters whch can follow the % sign.
FIGURE 2.5
This very simple program nevertheless illustrates a lot of important points about ‘C’. The
most important point is that the program consists entirely of calls to functions. There is
absolutely no processing in the main program, simply two calls to the printf function. The
brackets, which we did not use in main() are used in printf to carry the items that we want
to print, and also the instruction codes about how we want it all printed. This is the way of
carrying out most actions in ‘C’, and very often we have to write our own functions if there
is nothing suitable in the library.
Now press the ‘c’ key to get to the compiler, type #include to compile, CTRL z to signal
ready to run, and answer ‘y’ to the ‘Type y to run:’ message. You must, of course, press the
(RETURN) (CPC6128) or (ENTER) (CPC464,664) key each time. When your program
has been compiled and will run, it’s a good idea to check recording. To record yourprogram
on disc or tape, type pl0,50,test , then (ENTER). You can, of course, use your own
filename in place of test here. When the program has been saved, you can load it with
2-9
g„test , specifying the filename again. Note that the two commas must be used. If you
have only one drive, you don’t need to specify drive number with g, but you must use the
correct file name. You will find that the program is always renumbered when you list it,
with numbers starting at 10. This is not obvious in this example, but if you add some
comment lines such as:
25 /*odd line number 25*/
and then record this and load it in again, you will find that the renumbering has been
carried out.
If you use a word-processor program for writing and editing your ‘C’ program files, you will
not make use of line numbers until the program is loaded for compiling. You will see from
the disc directory that your ‘C’ program has been recorded as a file which is
indistinguishable from any other file in ASCII codes. This file is called the ‘source-code’.
Until this source code has been compiled it is just a file of ASCII codes, nothing more. Once
compiled, it is object code, closer to machine code and quite different in action. The most
important difference from your point of view is that the source code can be read, edited, and
is easily understood. The object code has no meaning unless you know about machine code,
is very difficult to edit, and can be recorded only when you are using the # translate
instruction inside the program code. The routine for ^translate compiling is summarised
in Figure 2.6.
1. Write the source code and test it thoroughly.
2. Edit in a first line which is #translate filename, using the filename that you want
for your machine code. Make sure that this name does not appear on any other file on
the disc.
3. Save the source-code on a disc.
4. Use the #include sourcefile command to read the source code from the disc and compile
it. The code will be compiled to machine-code, and stored on the same disc under the
filename that you used along with ^translate.
A summary of the procedures for using ^translate to make a ‘standalone’ program.
FIGURE 2.6
Even if you use a cassette system, there is no point in using ^translate for short programs.
This is because even a short program requires a large amount of code. The example of
Figure 2.3 requires about 3K of code when it is compiled to a combined BASIC and
machine code program which will run independently. Once the program has run, the
machine instantly returns to BASIC, and unless your program ends with a loop of some
kind, the results of running it are not visible. The source code for this sample program fills
only part of one sector, IK on the disc. The reason for the difference is that when you
2-10
compile and run in one operation, most of the code is already in memory, either in the RAM
or read from the disc or cassette. When you use the #translate instruction to create a
program which will run on any Amstrad, it has to include all of the code (the ‘run-time
system’) that would normally be held in memory as part of the ‘C’ compiler program. The
consolation is that longer programs do not necessarily need very much more code! From
now on, we will concentrate on examples which use ordinary compilation.
Using constants.
When you use a constant, like 3.1416, in BASIC, you are always advised to assign the value
to a variable name. The reason is that this avoids the BASIC interpreter having to convert
the ASCII codes for the number into number-variable form each time the number is used.
The same is true of‘C’ programs, but with the difference that you can either assign to a
variable name or use a ^define , of which more later. As far as HiSoft C is concerned, one of
the things that you have to get used to is that you can’t use numbers like 3.1416 - not with
VI.2 at least. The reason is that HiSoft C VI.2 does not allow what are called ‘floats’,
numbers which can contain fractions. This is one of the few missing parts of a complete ‘C’,
and we simply have to accept it. Originally, ‘C’ was designed to be used by programmers
who were writing compilers for other languages and for operating systems, and only integer
(whole) numbers were needed. Though standard ‘C’ can cope with fractions, HiSoft C
can’t in V1.2, though there are hints in the manual that this extension will follow.
Even with this omission, ‘C’ has rather more different types of data than BASIC, and one of
these is the integer, which uses the reserved name int. As we saw briefly in Chapter 1, you
can declare that a name will be used for an integer, and then assign a value to the integer.
The syntax of declaration is:
int penta;
using the reserved word int followed by the identifier name penta. You can have several
such declarations on the same line, with commas following each name. For example, you
could have a line:
int penta,hex,hept;
if you wanted to declare several names as integers. Note the semicolon to show the end of
the statement, the end of that declaration. Once the names are declared, you can make
assignments to these names, using integer numbers.
main t)
r
X
int hex;
hex=6;
print-f ( " \nXd times 2 is V.d“ , hex , hex*2)
5
"I
Using a variable, in this example an integer variable called hex.
FIGURE 2.7
2-11
Figure 2.7 shows a simple program which makes use of the integer hex to mean 6. In this
example, the declaration of the integer and its assignment are both straightforward, but the
printf line is not. In ‘C’, printing is a very different kind of operation as compared to
BASIC. The first part of the printf statement consists of the words and formatting
instruction only. We want two numbers to be printed, both in denary form. In the phrase
that is to be used, then, the %d is put in each part where a number will be printed in the
version we see on the screen. Once the quotes are closed, the numbers are put in, using the
same left to right order, and with commas used to separate the numbers. The numbers are
hex, the integer, and hex*2, the result of a calculation. Once again, this is a statement, and
it has to end with a semicolon. When it prints on the screen, you see the message:
6 times 2 is 12
which is not exactly world-shaking, but until you get used to the way in which ‘C’ uses its
printf statement, it’s an example you’ll probably need to consult now and again.
Constants.
The use of a variable for holding a number in ‘C’ is close enough to the methods of BASIC
(so far) to cause you little worry. There is an alternative in ‘C’, however, for storing items
which you might want to use in any part of a program. These items are called, logically
enough, constants, and they have to be defined in a way that is quite different from our
definition of variables. The definition of a constant is done at the beginning of a program,
before the main() portion or (almost) anything else. The syntax is simple enough, #deflne
, followed by a space and then the name that you want to use, another space and the value.
There must be no semicolon at the end of a #define line. Constants can be numbers, single
characters or strings, as you please, providing you assign correctly and use correctly. In
other versions of‘C’, this part of the programming is handled by a separate section, called
the ‘pre-processor’. In HiSoft C, it’s all part of the main compiler action, and we’ll treat it as
such. Take a look, for example, at Figure 2.8.
#define -foot 305
main ()
C
int ft;
f t=3;
printf("\nXd feet is %d mii1imetres",ft,
ft*foot);
Using a constant, declared with ^define. This is one of the ‘pre-processor’ actions.
FIGURE 2.8
2-12
The line which ‘declares the constant’ of foot is situated immediately at the start of the
program, using #define foot 305. In the main program, the part which lies between the
curly brackets, we will use foot as meaning the number 305, the approximate number of
millimetres in a foot. This meaning must be declared before the program begins. This way,
the compiler has allocated memory space for the constant and is ready to use it before the
program needs it. As we have seen, you might have to allocate several constants like this
before a program starts. These constants need not all be integer numbers like 305. They
could be letters or phrases, like ‘Press any key’, and this use of a constant replaces a lot of the
purposes for which we use string variables in BASIC. This is important, because ‘C’ does
not have string variables in the form that we use in BASIC. In the example, you can see
the printf phrase “ \ n%d feet is %d millimetres” used with the %d to specify where the
numbers will be printed, as denary numbers. Following the phrase comes the quantities,
the integer variable ft and the constant foot. In this example, the numbers have been
printed as denary numbers, but you can force any printf action to produce numbers in
other forms, such as hex or octal, that you want. You can also decide how much space you
want the number to take up. Try a change to line 60, so that it reads:
60 printf(“ \ n%^-6d feet is %8d millimetres”,ft,ft*foot);
and compile and run this one. You will see that the figure ‘3’ appears on the left hand side of
the screen, and the number 915 is spaced out from the word ‘is’, taking up 8 character
positions. As you may have guessed, the figure 8 along with the ‘d’ specifies that the number
shall be printed taking up 8 character positions, and placed at the right hand side (right-
justified). By using a minus sign in front of the number, the space is allocated similarly, but
the number is set over to the left (left-justified). This type of control over number position
is called ‘fielding’, and it’s much easier in ‘C’ than it is in BASIC. If you don’t use any
numbers along with the ‘d’ in formatting, a number will simply take up whatever space it
needs in the printf statement. You can, of course, decide on the number of spaces either
ahead of or following the number by putting them in with the spacebar. The fielding
method is particularly useful if you want numbers organised in columns, and really comes
into its own when fractions can be used. In a later version, perhaps...
I said that a constant did not have to be a number, but could be a character or a string. For
items like that, you still use ^define, with the name that you want to allocate, and the
character or string spaced from it.
#detine niesg "press any key"
#de-fine key "Y"
main <)
{
print-f < "\nuse the /is key or", key);
print-f ("\n7.s" , mesgi ;
>
Using string and character constants with ^define
FIGURE 2.9
2-13
The character or string needs to be surrounded by quotes, as Figure 2.9 shows. If you omit
the quotes you will get an error message during compiling when the character or string is
used, rather than where it is defined. The message will be ‘undefined variable’, but if you
surround the characters with marks other than quotes you can get some quite exotic error
messages. The other part of the deal is that if you want to print messages in this way, the
printf statement needs to be changed. In place of the %d that you needed to specify a
number in denary form, you need to use %s for a string or %c for a single character. Without
these modifiers, nothing gets printed! In the example, %& has been used for both, but we
could have used %c for the single character. The use of #define in this way allows a lot
more than just the occasional number constant or message phrase. With #define , you can
make your programs much more readable, particularly by definitions such as ^define
white 0 and ^define black 1 , which allow you to use words in place of numbers for items
such as board games, or allocate values to items, as in #define mayfair “£5000”.
More variables.
We have already made use of an integer variable in a program, and the style is easy enough.
However, there’s much more to this type of variable than meets the eye. A variable which is
declared as, for example, int num is what is called an ‘automatic’ variable. In all varieties
of‘C’, you can state this by typing auto int num, but if you don’t use any word before int,
then the use of auto is assumed (it’s the default). Most of the variables that you are likely to
use in HiSoft C prgrams will be auto types, simply because of convenience. The alternative,
as far as HiSoft C is concerned, is a static variable. Now the difference is not at all easy to
understand if you have only ever programmed in BASIC, and a more extended explanation
will follow later when we deal with functions. The difference concerns how values are
stored and used. The storage of a static variable in HiSoft C is more efficient and the value
can be reached much more quickly. You might, for example, want to use static variables in
games programs to get the highest possible speed. As far as programming is concerned,
though, the important difference is retaining values. All variables in HiSoft C are local.
This means that they have a meaning only within a function in which they have been
declared, and in any function contained within that. So far, we have used only a mainQ
function, and the point is not important - yet. When a variable is declared in a function that
is called by main, any value that was allocated to it disappears whenever the function ends.
You can use the same variable again in main() with a different value without confusing the
machine. If you return to the function, the assignment starts all over again. If the variable is
static, though, its value is held waiting to be used again. If you return to a function in which
a variable has been declared as static, and in which it had the value 5, then the value of 5
will be assigned to it whenever the variable is used again. There is an illustration of this in
Chapter 10. For the moment, however, the static option is one that you should think of
simply as a convenient way of speeding up a program, and we’ll look at the other
implications later. One feature which might be useful right away, however, is initialisation.
The way that you can declare and assign in one operation in BASIC, such as A%=5, is very
useful. In HiSoft C, you can’t do this with automatic variables, but you can with statics.
Take a look at the very brief example in Figure 2.10.
2-14
main <)
static int dot=6;
printf ( "Nn'/.d" , dot) j
Combining declaration and assignment for a static integer.
FIGURE 2.10
This declares a static int and makes the assignment of 6 to its value. The printf statement
then shows that the assignment has been carried out. If you attempt to do this with an auto
variable, as by deleting the word static, then you get a RESTRICTION error message. As
this suggests, this restriction is peculiar to HiSoft C, and you can initialise automatic
variables in some other versions of‘C’. It’s really another good reason for preferring static
variables. If you have read some other books on ‘C’ (and the best of luck!), you may have
seen references to register variables and extern variables. These are not available in
HiSoft C, and I don’t think you’ll miss them!
Getting a value.
Suppose that we extend the use of an integer variable to a variable whose value is entered
from the keyboard? One of the standard identifier words for reading an input is getchar(),
which is a function that is built-in. Being built-in means that we don’t have to worry about
the complications of reading source-code from the library. It’s the old Sam Goldwyn
motto, simplicate and add lightness, in action! Using this built-in function, however,
brings us up against one of the features that newcomers to ‘C’ find irritating - the use of
characters. The function getchar() will get characters from the keyboard, meaning that
you can type any character, digit or letter that you like. If you look up the action of this
function in the HiSoft Manual, however, you see it described as int getchar() meaning
that it gives you (or returns) an integer. Whatever you type at this point is accepted as an
ASCII code, and this is the integer value that you get. What we are going to type is a number
which will have to be assigned to a variable name of x. The value of x, however, will be an
ASCII code. For the numbers 1 to 9, this means a code in the range 49 to 57. We can get the
number values back from this by subtracting 48, the value of ASCII ‘O’. Now we can do this
in two ways.
main ()
r
int x j
pri nt-f < "Enter a number, 1 to 9 \n">;
x=getchar ();
x=x—48;
print-f ("\nSquare is %d",x*x);
n
J
A program which obtains a character from the keyboard.
FIGURE 2.11
2-15
In the printed program of Figure 2.11, it is shown in the familiar BASIC way, as x=x-48.
We can, however, also write it as x=x-‘0’ , meaning that we subtract the ASCII code for
zero. This second form is a lot easier to use and understand - for one thing, you don’t have
to strain your memory for the ASCII codes!
Using getchar(), as you’ll gather, is rather primitive. Though you can type more than one
digit, the function works only on the first, which is why the program limits input to the
range 1 to 9. There is a function called atoi() in the library for converting characters into
numbers, but that’s for later. There is also, in the library, a routine which corresponds
more closely to BASIC’s INPUT, but without the facility to mix questions and input like
INPUT “Answer: ”;a$ in BASIC. The trouble with using these routines right at the start of
your conversion to ‘C’ is that they involve a lot of new ideas, and we can’t ever take in too
much at one time.
Other variable types.
By the time any book on BASIC has reached this stage, the subject of string variables would
have appeared. Now strings have an important part to play in ‘C’, as they have in any
language, but the way that strings are handled is not quite so simple if you are making a
transition from BASIC to ‘C’. The reason is that STRING is not a pre-defined variable
type; it isn’t in the list of ready-made identifiers of Figure 2.2. We’ve looked already at how
we can use a string in a ^define line, and when you think about the way you use strings, this
probably takes care of more than 60% of the ways in which you use strings in most of your
programs. Later, we’ll look at how this type of identifier can be created, but for the moment
we’ll look at the characters that make up a string.
HiSoft C, in common with all others, has a variable type called char, which means any
character of the computer which is represented by an ASCII code. In HiSoft C for the
Amstrad, this includes the graphics characters as well as the ordinary alphabetical and digit
characters. Now with this char variable, we can do the actions that you associate with
PRINT CHR$() in BASIC, and some more. Take a look at Figure 2.12, for example.
ma inf)
static char ml=249;
static char m2=250;
static char m3=251;
rawout(4);r awout(1);
print-f ( "\n 7.c 7.c */.c"
ml,m2,m3);
Printing three characters on the screen using rawout.
FIGURE 2.12
2-16
Three static char types are defined, variables ml, m2 and m3. These are assigned with
ASCII codes 249 to 251, just to rub in the point that the whole of the Amstrad ASCII codes
can be used. The use of char means that the variable consists of one ASCII code, stored in
one byte of memory. Printing these codes will give the screen patterns that correspond to
them, the ‘running men’ shapes. As it happens, however, these will be in the Mode 2 size,
which makes them very difficult to see (like the text). The line which contains
rawout(4);rawout(l); will switch the screen to Mode 1, making both text and character
shapes a lot easier to see on the screen.
1. In edit mode, type #direct+ and press (RETURN)/(ENTER) key.
2. Type rawout(4);rawout(l);direct-; then press (RETURN)/(ENTER).
3. You can now enter your source code in Mode 1, which is much easier to read. The mode
will return to Mode 2 when you compile, but this also can be changed, see later.
Switching over to Mode 1 with a ^direct routine. The mode will return to Mode 2 when you compile.
FIGURE 2.13
Figure 2.13 shows how you can switch over to Mode 1 by a direct command, making it
easier to type and check your listings. Note that the screen mode will always change back to
Mode 2 when you press ‘c’ to compile, and a fix for that particular problem is listed in the
following chapter.
Finally, what about using rawout() for characters. The rawout function has to be
provided with an ASCII code between its brackets - this is the ‘argument’ for the function.
Whatever this ASCII code represents will then be ‘printed’ in the usual Amstrad way. As
far as the use of codes 4 and 1 are concerned, these are the codes for notifying a mode change
and specifying that the mode is Mode 1. We could just as easily use rawout() with the
variables ml, m2, and m3, even though these have been defined as characters rather than
integers. This is the kind of flexibility that is the joy of‘C’ programmers and the despair of
academics! The only snag with using rawout() is that you don’t have the same control over
how the characters are presented on the screen as you have with printf(). Try removing
the printf line, and substituting:
rawout(ml);
rawout(m2);
rawout(m3);
to see the effect. It still prints on the screen, but the characters are closed up against each
other.
2-17
Chapter 3
Functions and the Library.
Functions.
We have seen already that a program consists of any ^define constants, then a main()
which is followed by the opening curly bracket, some statements which end with a
semicolon, and then a closing curly bracket. In the main program, between the curly
brackets, you will place all the actions, in sequence, that the program will carry out. Now in
short programs these actions will be simple ones, and they can all be written between the
curly brackets of the main program. As your programs become longer, however, you will
need to break them into sections, if only for the sake of planning. Just as you can break a
BASIC program into a core and a set of subroutines, or functions, you can break a ‘C’
program into a main block and a number of functions. The similarity between the
languages ends there, though. A function in ‘C’ is called into action by using a name, its
identifier. In addition, values can be passed to a function in ways that are not used by
subroutines. The use of a function is therefore something that needs rather more thought
than the use of a subroutine. Unlike BASIC, ‘C’ permits only functions, and there is
nothing remotely like a subroutine. If you have programmed with functions in Locomotive
BASIC, you will feel very much at home with the way that ‘C’ uses functions.
Take a look for example at the program in Figure 3.1.
main ()
X
printf("\nThe name is ");
attention (T’l;
}
attention(n)
char nj
r
X
rawout(7) ;
print-f (7.c",n) ;
print-f (" Sinclair.");
rawout(7);
>
A program which makes use of a new function.
FIGURE 3.1
This starts in the usual way, and prints a phrase, ‘The name is ’. It then calls a function
attention. Now this isn’t a function that is built in to the compiler, nor held in the library.
3-1
It’s a function that we have to write for ourselves. The name of the function is attention,
and it will make use of anything that is enclosed in the brackets. What is in the brackets in
this example is the character T. Note that this is T with a single apostrophe, not “I” with
quotes. The difference is important. The T is a character with ASCII code of 73, whereas
“I” would represent a string- and a string is not a single character as we’ll see later. The ‘I’
is the argument of function attention, the value that has to be passed for the function to
use.
Now of course, we can’t compile this and run it until there is a function attention written.
The main() program is ended by the second curly bracket, and following it we type the
name of the function, which is attention(n). We can make the letter inside the bracket
anything we like, it can be a complete name or a single letter. We then have to declare that
we will be using this quantity n, and that it’s a character. The curly bracket then opens, the
rawout(7) delivers a beep (equivalent to PRINT CHR$(7) in BASIC, remember), and the
character is printed. Now this is represented in the first printf line as n. This is a variable
name which exists only inside this function, and because n was used in the ‘header’, as the
argument of attention(), it takes the value that was used as an argument when the
function was called, which is ‘I’. The printf modifier is %c, meaning that the variable
which follows will be printed as a single character. The next line is a conventional printf -
notice that we don’t need a modifier because we don’t want a new line, and we aren’t
printing a variable, just a phrase within quotes. The last part of the function is another
beep, which in fact will just sound as part of the first one because there is so little time
between them.
Now when you compile and run, you see the complete phrase appear on the screen. It
would, of course, have been just as easy to use the whole phrase in the printf line of the
main program, but it’s always easier to see how something works from a simple example
than it is from a difficult one. The important point is that any function you want to use can
be called up by using its name, and the brackets are used to pass any arguments to the
function. These arguments can be integers, characters or strings, direct or as variable
names. When the function itself is written, you write it like another main() type of
program. You need to declare any variables that you want to use inside the function, and
that includes the name that you have used for the argument. You then carry out whatever
actions are needed. In this example, this has meant calling up other functions, rawout and
printf, which exist in the memory of the machine along with the rest of the compiler. This
is typical of the way that we write programs in ‘C’ - a small main() program calls up
functions, which in turn call other functions and so on. Unless you use cassettes, you can
use the CP/M TYPE instruction to list the version of Reversi that you get with the HiSoft
compiler, and you’ll see from that how remarkably short the main program is.
The variable n exists only within the attention function. If you try to print its value near
the end of the program, following the attention(T); line, you will find that you get an
error message about ‘undefined variable’ when you try to compile. This means that n was
not declared as a variable at the start of the main() program. The fact that it was declared
as a char type in the function refers to the function only, and you can print the value of n at
any time while the function attention is running. The significance of this is that you
provided a value of‘I’ as the argument for attention. This value is then temporarily
transferred to variable n for the duration of the function only. This is completely
automatic, and is rather different from the methods that you have to use in older varieties of
3-2
BASIC. The name that is used for n and anything of this kind is a local variable, and there is
no equivalent to the use of this type of variable in Locomotive BASIC. All variables that are
declared inside a ‘C’ function definition are automatically made local. We’ll look later at the
principle of passing values back from a function, and of using global variables, whose values
are retained in all parts of a program.
The library.
The library is one of the glories of‘C’, because it’s the way in which the language can be
continually extended and made more useful. The library is a set of functions, written in
source code. Its value is that any function in the library can be named in your own program,
and taken from disc or cassette to be included in your program. In many versions of‘C’, this
has to be done by loading in the whole of the library routines before you compile. HiSoft C
has a very useful variation on library use which allows you to load in only what you need.
This means that the disc or cassette has to be searched, and this takes a noticable time, even
for a disc. As with any compiled language, however, the time you spend on this part saves
time later when you run the program. At this stage, we’re not ready to make the most
efficient use of the several library files that HiSoft has provided, but it’s a good point to start
looking at how the library is used.
To start with, there are two really important library files that you need to know about. The
first one is called stdio.h, and the second is called stdio.lib. You will normally need both
of them, and you certainly can’t use stdio.lib unless you already have stdio.h in place.
The positions in which these are called are also important. The header stdio.h must be
placed right at the start of a program, before anything else, even before the ^define lines.
The header is installed by typing ^include stdio.h. The use of ^include with a filename
like this causes the compiler to look on the disc (or cassette) for the routines. The other set,
stdio.lib goes right at the end of all your program sections. Now you will usually want to
take only what you need from this one, so its installed by typing #include Pstdio.lib?, The
question marks are a feature of HiSoft C which you will not find on other versions, and
their use for selective inclusion saves a lot of valuable memory, a feature that you’ll
appreciate if you are using the older Amstrad machines like the CPC464 or CPC664.
The next step is to decide what to use from the library. One useful function is the one that
provides the poke action, and its syntax is almost identical to the use of POKE in BASIC.
On the V1.2 version of the HiSoft compiler, poking location 3551 with 1 will switch to
Mode 1, making the load on your eyes a lot easier.
/* Set mode 1 -*/
ffinclude stdio.h
main <)
{
poke(3551,1);
#include ?stdio.lib?
A program which sets Mode 1, using the library. This will remain set until you switch off, or change the
number stored in address 3551.
FIGURE 3.2
3-3
This has to be done with the aid of the library, however, so we start the program of Figure
3.2, after the reminder line, with ^include stdio.h, and end it with ^include Pstdio.lib?.
The rest simply consists of a main() that has just the one statement, poke(3551,l).
Compiling this takes a considerable time, because the library has to be searched. Even on
disc this takes time, and I would want to make a cup of coffee if I were waiting for a cassette
to do this. When the program runs, you don’t at first notice any change. That’s because the
mode number is not used until a ‘c’ is typed. When you type ‘c’, then (RETURN), you’ll see
the mode change to Mode 1. This will persist for as long as you use the compiler, unlike the
change which was noted in Chapter 2. If you happen to know how to make a new recording
of your version of the compiler, you can now record it with this change in place. If you don’t
know how to do this, then it’s not my place to tell you!
That’s just one example of the use of the library. It’s a long winded one, because only one
library function has been used after all that disc spinning. If you want to use just one library
function, the easiest way is to print out the function that you need and simply copy the
library routine into your main routine. This will then compile at once, with no need to
include either the header or the main library. If you want to do this with the poke action,
Figure 3.3 shows the result.
/* Set mode 1 */
main ()
typede-f char * _char_ptrj
int address,valuej
address=3551;
value =1;
♦cast(_char_ptr) address = value;
The program for setting Mode 1 with its routine extracted from the library (courtesy of HiSoft). This makes
compiling much quicker.
FIGURE 3.3
This is simplified, because only fixed values are needed - but don’t ask how it works just at
this point! Check that it works, then save the code under a filename like model, and use it
each time you have loaded in ‘C’.
Look now at another couple of library actions. These are max and min, and as the name
suggests, they will weed out numbers from a list. The list must follow the max or min
words, within brackets, and the result of the search has to be an integer number. The max
and min functions are in the stdio.h part, and if you don’t need to use any of the functions
from the main library, then you needn’t run it. Figure 3.4 shows max and min used in a
simple example.
3-4
#include stdio.h
main ()
r
\
int k;
k=max (23,1,45,67,22,34) ;
prints ("\n Max is 7.d",k)j
k=min(23,1,45,67,22,34);
print-F(“\n min is Xd" , k);
Using the max and min functions from the stdio.h library.
FIGURE 3.4
You can, incidentally, speed up the compiling of this and other examples once you have
checked that they operate correctly. If you put #list- at the start of a program, it will
suppress any listing. You can see this command being used when the stdio.h program is
being loaded in. Placing #list+ at the end of the program turns the listing on again so that
you can use the editor normally. This avoids having to watch all the titles come up in the
library routines, and it’s particularly useful for long programs.
As a last example of the use of the library for the moment, take a look at Figure 3.5.
#include stdio.h
#de-fine sample ”123fg"
main ()
{
int val;
val=atoi(sample);
print-f ( "\nValue is 7.d‘‘ , val ) ;
>
#include stdio.lib
Using the function atoi from the library. This carries out the action of VAL in BASIC.
FIGURE 3.5
This defines a string constant as “123fg”. The stdio routines are used, and the particular
routine here is a very useful one, atoi, which does the job that VAL does in BASIC. This is
to convert a string into an integer number. Only the number characters at the start of the
string are converted, just like the VAL action. In this example, the string is a string
constant, and the atoi action converts it into an integer 123, which is printed in the usual
way. There are many more functions which will act on strings, but before we can make
really effective use of them, we need to be able to work with pointers, -the crowning glory of
‘C’. That’s for later!
3-5
Planning.
One of the most important points about functions is how they affect planning of programs.
How, for example, do we plan the simple illustration of Figure 3.1? Figure 3.6 illustrates a
version of one method which is favoured by many programmers.
Program
Start
Initialise
message
Call attention
{ print letters
print string
% End
A planning system that is very popular with programmers.
FIGURE 3.6
The left-hand side shows the steps of the main program, with the main action shown as one
step. The curly brackets are then used to show where more details are needed. This has
been used in the example to show variable names, and also to show what has to be done in
the attention function. The important point is that a function is designed in very much the
same way as the main program is designed. You can design a function without constantly
having to refer to the main program, because the variables that are used within a function
need not bear the same names as those used in the main program, the only essential feature
is that they should be of the same type. In general, if you define variables for the main
program at the start of the program, these variables can also be used in the function. If you
define variables inside a function, these variables are ‘local’; they are used only in the
function, and simply don’t exist when the function is not running.
Some operations.
There aren’t many programs that you are likely to write that don’t involve the operators of
‘C’. The operators are the symbols which control actions on numbers, and ‘C’ is rather
richer in operators than BASIC. In addition, some of the actions and the order in which
they are carried out need rather more thought than you would give to similar things in
BASIC. The four main operators of * / + - are specified just as they are in BASIC, but you
have to remember the restriction of integer numbers in HiSoft C VI.2. Look at an example
to start with, in Figure 3.7.
3-6
main ()
i nt w,x,y,z;
'< = *-> ?
y=5;
2—7 ;
printf ( 11 \n7.d divided by /id gives 7.d and
7.d over “ , z , x , z /x , z 7.x ) ;
w=++x ; w==++w;
print-f (“ \nw is Y.ti and x is 7.d",w,x>;
Integer arithmetic, showing the whole number result and the remainder or modulus.
FIGURE 3.7
This shows four integer variables declared, with three of them assigned. The first printf
line will print values of z, x, z/x and z%x. Now z/x is just z divided by x, as you would expect,
but this is integer division. Hisoft-C in its VI.2 form does not support ‘floats’, meaning
numbers which contain fractions, so only the whole number part of 7/3 is printed, and
that’s 2. The % operator, however, is used to find a remainder. The expression z%x means
‘find the remainder after z has been divided by x’, and this amount will be 1. The use of
these two operators, then, allows you to carry out divisions and show the result as a number
and a (vulgar) fraction, whereas using a float number would allow the result to be shown as
a number and decimal fraction. Try altering the printf line so that the program will
produce:
7 divided by 3 gives 2-1/3
- this is a good way of checking that you have really understood how printf works!
The next line carries out an action which is quite certainly unfamiliar in BASIC. The
assignment w=++x means that the value of x is incremented, and it is then assigned to
variable w. The next part of the line then increments this value of w and assigns it to w. The
printed values of x and w then show that has been done. The ++ operator means
increment (increase by 1) and the — sign means decrement (decrease by 1). Just as
important, however, is the point that the position of these symbols is important. Edit the
assignment line now so that it reads:
w=x++; w=++w;
and run this. The printout now states that w is 4 and x is 4. Using the increment sign
following the variable name means that the increment action has been carried out after
the assignment, not before. If you make the line read:
w=x++;w=w++;
3-7
then the result is ‘w is 3 and x is 4’, because w=x++ made w=3, and then x=4, while
w=w++ made w equal to 3, and then carried out an increment action which had no effect
because the variable had already been assigned. The use of increment and decrement can be
very handy in loops, but you have to think out the order of things carefully. This becomes
more difficult when you get to complicated expressions, so it’s advisable to start with the
easy ones.
Operator Action
++
Increment variable value.
—
Decrement variable value.
*
Multiply quantities.
/
Divide (integer result).
%
Modulus, remainder of division.
+
Add quantities.
-
Subtract quantities.
<
Less than.
>
Greater than.
<=
Less than or equal to.
>=
Greater than or equal to.
Identical to.
;
Not equal to.
&&
AND action.
1 1
OR action.
?;
Select one or other.
=
Assign value.
The ordinary arithmetic and logic operators. See Chapter 10 for the bit operators.
FIGURE 3.8
Figure 3.8 gives a list of the ordinary operator symbols that are used in arithmetic or logic,
and their actions. This is not a complete list of all operators, because there are operators
which act on pointers, and others which operate on the bits of a byte, but these don’t
concern us at the moment.
3-8
raain ()
t
int a,b,w,x,y,z;
test<x , y > ;
test (x , z ) ;
i-f (y!=z) pri nt+ < “\n'/.d not equal to 7. d",
y,z>;
x *—£>;
printt ("\nx is now 7. d‘‘,x);
"1
J
test <a, b )
int a,b;
f
it <a==b> printt i“ \nV.d equals 7-d“,a,b);
else pr i ntt ( ‘“\n7.d is not equal to %d" ,a,
b) ;
J
Examples of use of some operators.
FIGURE 3.9
Figure 3.9 shows another example of some of the operators which will be less familiar to
you, along with an introduction to the ‘if test. The usual declarations of variables are made,
then assignments, and then a function is used to compare first x and y, then x and z. In this
function, test, the numbers which are to be compared are supplied in variable form as
arguments in the brackets, separated by a comma. The test function itself uses integers a
and b, which are allocated with values only in the function. We have declared a and b in the
main program also, so that we could, if we liked, use a and b to hold quite different numbers
in the main program, unaffected by the use of a and b in test(). The test is for equality, and
the operator which is used is ==, not the more familiar = of BASIC. In ‘C’, = is used for
assignment, and == means ‘identical to’, avoiding the confusion that can arise in BASIC,
which uses the same symbol for both purposes. The test routine will print a message if the
two quantities are equal (which they never are), and another message if they are not equal.
Notice how this second message is obtained. The if test line is followed by another line
which starts with else, and this is followed by the action which is to be taken when the first
test turns out to be false. The item which is to be tested is enclosed in brackets, like
(a==b), and this expression will be either TRUE or FALSE. If it is TRUE, then whatever
immediately follows the test will be carried out. If the expression is FALSE, the program
moves to the next line to carry out the effect of an else if one exists. If there is no else, then
whatever follows the if line will be executed. You can have complicated sets of if and else
lines with nesting, but for the moment we’ll concentrate on the simple examples. When
these tests have been carried out, there is another test in the main program which tests for
(y!=z). This means y not equal to z, and the != combination in ‘C’ is the equivalent of
<> in BASIC. Finally comes the expression that is decidedly peculiar to ‘C’. Using x*=6
is the equivalent of x=x*6, a combination of the operation of multiplication and an
assignment. This kind of shorthand action is what gives ‘C’ a reputation for being difficult
to follow. The point is that you aren’t forced to use x*=6 in place of x=x*6, but when
you’re familiar with these shortcuts these are very convenient.
3-9
More planning.
Let’s look now at a short program, starting with the design steps. This is to be a program
which will convert a list of Celsius temperatures into their Fahrenheit equivalents. The
planning, such as it is, is shown in Figure 3.10.
Program
Start
Declare integers f,c
Loop c=0 to c=100 step 10
Action f=(c*9/5)+32
print results
End
Planning a simple program to convert Celsius temperatures into Fahrenheit.
FIGURE 3.10
On the left-hand side, the main steps of the program are listed as Start, declare, loop, action
and End. The curly brackets now open into more detail. The variables will be called f and c
and will be integer numbers (not much choice with VI.2). The range and steps will be dealt
with by using a loop, which is something new for us. The only detail which is included in
this list is the range of temperatures, 0 to 100 in steps of 10 Celsius degrees. Fortunately,
this range allows exact conversions, so that we don’t have to show fractions of a degree, but
if we did, then you know how to do this. The conversion could be done by a function, but it’s
so simple that it’s hardly worth while, and we simply use the conversion formula.
Now this program illustrates that a FOR loop in ‘C’ takes a very different form, particularly
as regards the STEP portion. Figure 3.11 shows the program which has been drawn up
from the plan.
matn ()
f
c
int i ,c;
-for (c=8; c< = isS0; c=c+10>
'L
t = C C * V / O ) + ;
print-f (“\n%d C is 'id
The Celsius to Fahrenheit program for a range of temperatures.
FIGURE 3.11
3-10
As always, looking at a finished program gives you no idea of what the steps were in writing
it, so we’ll look at the program in the order in which it was written. When you write a ‘C’
program from a plan, never allocate any line numbers. Leave the allocation of line numbers
to the i action of the compiler when you enter the program. This avoids continual re¬
numbering when you have to squeeze yet another definition into the start of the program at
the planning stages! The main program starts in the usual way with main(), and then
declares the integers f and c. The next step is the loop which carries out the actions of
converting and printing the values. Now the first thing to note here is that the loop line does
not end with a semicolon. This is because the statement has not ended; we have to specify
what will be done in the loop, and that follows enclosed in curly brackets. The effect of
enclosing the two statements in curly brackets is to make this set of lines constitute one
single statement. A set like this is often called a ‘compound statement’, and because it ends
with a closing curly bracket, it doesn’t need a semicolon. What is inside this set of curly
brackets, then, will be carried out on each pass through the loop.
The next thing to look at very carefully is how the loop statement is constructed. In many
ways, this corresponds exactly to the BASIC statement:
FOR C=0 TO 100 STEP 10
but ‘C’ writes this as a set of conditions. The first test is c=0, the starting condition for the
loop. The next is c<=100, meaning c less then 100 or equal to 100. This is the ending
condition. The third is c=c+10, and this is the stepping condition. If you think that is all
very clear and straightforward, then try omitting the step condition. You’ll find that the
loop is then endless, and you need to use ESC to get out of it. Unless you put in a step
condition, there won’t be one, and the loop will be endless, unless c gets incremented
somewhere within the curly brackets following the loop statement. The other difference
from BASIC is the condition c<=100. If you try making this c=100, you’ll find the loop
goes only as far as 90, because after 90, c is not less than 100. Now try making the middle
condition c=100, and see what this does. The effect, another endless loop, is most
unexpected if you are still thinking in BASIC terms. The reason is that each part of the loop
statement is a condition. The c=0 part is a starting condition. The c<=100 is an ending
condition, but the loop does not end until this condition is FALSE. If you put in a condition
which makes the loop impossible, the result is an endless loop. With c=0 (and c=100) false,
the loop should not run, and the response is to increment c to 100, then run continuously!
The conditions c<number, c>number, c<= number, or c>=number are the ones
that you should always use for a loop of this type. Since ‘C’ offers you the choice of two
other loops, you can’t really complain that you are restricted for choice.
The conversion to Fahrenheit uses the variable name f, and is the first action in the loop.
The next action is to print both amounts. With the range of quantities that we have chosen,
there will never be a fractional result, so that the answers are exact. The way that the
numbers are presented, however, could be improved. One way is to use left-justification,
and this can be done by using the line:
printf(“\ n%-3d C is %d F”,c,f);
3-11
which lines up the printing of the words. This isn’t perfect, though, because we don’t
normally left-justify numbers. A better display is obtained by using:
printf(“ \ n %3d C is %d F”,c,f);
which looks a lot better. Three spaces have been typed between ‘n’ and and the number
has been right-justified to three figures. This puts the number always hard against the right
side of the spaces that you have left for it, and makes the display look just right.
While we have a loop operating, we can take the chance to make some changes which will
illustrate a few more points about how ‘C’ uses loops. In particular, ‘C’ has statements that
allow you to skip passes through the loop, or to break out of the loop, without giving the
computer’s operating system apoplexy. BASIC is not nearly so well organised in this
respect. Take a look at Fig.3.12.
main t)
i
int f , c;
for (c=0; c<=100; c=c+10>
r
t.
if (c==20) continue;
if (c==70> break;
printf ( "\n 7L3d C is 7.d F",c,f);
The use of break and continue in a loop.
FIGURE 3.12
Two lines have been added within the loop this time, and when you run the program you’ll
see that each has a very interesting effect. The continue statement makes certain that
nothing more is done, the program action returns to the loop step for the next pass. In this
example, no calculation is made, and nothing is printed. This is a way of excepting certain
items (even-numbers, short names, one particular name) from being treated by the action
of a loop. You can choose your position for the continue, too, because you might want to do
some of the loop actions before you skipped the rest. The other loop modifier is break.
This, as you might expect, allows you to break out of the loop altogether as the result of
some test. In this example, it’s when the value of c reaches 70, so that the loop prints out
only as far as 60C. In this example, of course, it makes little sense to do this, because we
could just as easily have put this in as the ending condition at the start of the loop. You
might, however, want to test for another condition, such as something non-numerical, in
this way. If your loop was, for example, reading a fist of 100 names, you might possibly want
to stop when you found McTavish, knowing that this name could be anywhere in the list.
The continue and break actions of‘C’ avoid the messy and unpredictable effects of using
GOTO’s in BASIC loops.
3-12
More functions,
It’s time to take another look at a function action, one which is simple but rewarding to
consider. This time, as the listing ofFigure 3.13 shows, the function is called (or invoked) as
part of a printf statement.
main ()
i nt x;
■for (x=0;x<=20;x++)
printf (“ \n'/.ri squared is '/.a , x , square (x ) )
square (a)
int a;
\
return(a*a;;
J
A function which returns a number to the main program.
FIGURE 3.13
The other important point which this program illustrates is one way in which a value can be
passed back from a function. This is not quite as straightforward as you might think,
because in the examples we have used so far, any quantity that is to be passed to the
function has to be defined in the function. If it is defined in the function, however, its value
is lost when the function ends! All of the functions that we have used so far have printed out
whatever value they calculated. In this sense, we have been using the functions in the way
that other languages use procedures, a way of doing some action rather than a way of
returning a variable value.
The illustration in Figure 3.13 is quite different. The loop makes use of numbers from 0 to
20, and the incrementing is taken care of by using x++ as the third term in the for
statement. Since there is only one statement in the loop, it can follow the for part, and be
terminated with a semicolon which now marks the end of the loop. If you put the semicolon
after the for statement you’ll get a loop, but with nothing in it! The printf statement prints
the value ofx, and also the value ofsquare(x). Now square(x) is a function, so we have to
define it somewhere, but the main point is how it comes to have a number value.
The answer is in the line return(a*a);. This means that a value is to be given to the
function, and that value is a*a, the square of a number which has been assigned to variable
a. Remember that a is local to the function, it has no value in the main program. The value
of x is passed to a when the function starts, and the function therefore acquires the value of
x*x at the return step - there is no change to the value of x. As an exercise, you might like
to tidy up the screen presentation of this little lot. Incidentally, if you have return; as a
statement by itself in a function, this is where the function will stop and return, but in this
case without a value. It’s the equivalent of the way that a BASIC subroutine uses
RETURN.
3-13
Now you need not feel that you must pass variable values to a function in this way. You
could just as easily use x as the variable all along the way, as the slight modificiations in
Figure 3.14 show.
main (>
r
i nt x ;
for (x=0;x<=20;x++)
print-f ("\nXd squared i s */.d x , square <x ) )
5
printf ("\n x is %d“,x>;
n
j
square(x>
't-
return (x*x);
Using a global variable in the function.
FIGURE 3.14
In this case, variable x has been used for both actions, both in the main program and in the
function. Because x has a value in the main program, both the name x and its value can be
passed to the function to be used. You cannot, however, omit this passing step. The value of
x itself is not altered by the program, as the final printed value shows. You can’t use
square() in the printf statement or in the definition of the function. If you omit the (x)
part, then x is not recognised in the function. Your compilation will then stop with the error
message of ERROR 37 undefined variable. You may have declared x in the main
program, but each function is a rule unto itself and doesn’t recognise any meaning to x
unless it is passed on, or declared locally. If you try to get round this by keeping the
square() use and adding int x; within the brackets of the function, you are in for more
trouble. The program will now compile, but what you see when it prints out is simply
gibberish numbers. Declaring x in the function makes a local x available, and it doesn’t get
assigned with a value. What you see when you print its square is just what happens when
the contents of memory that this variable uses is squared. This is a feature of‘C’ that you
need to be careful about. You can do things like this which are silly, but which follow the
rules of the compiler. Because they follow the rules of the compiler, they are accepted and
compiled, but when they run, the answers are garbage. Unless you know that the answers
are garbage, though, you might not notice. It’s important, whatever your state of expertise,
to test programs with items that can be verified reasonably easily - but you ought to know
that already.
3-14
Suppose that you want to return several values from a function. The return statement
does not cater for this, and there’s no simple answer at the moment. In other varieties of‘C’
you can use variables that have been declared as extern which you can pass to and fro as
much as you like, with values changed as you want. HiSoft C does not use extern in this
sense, and you have to live with it. As it happens, you very seldom need to pass back a set of
values. If you are printing numbers, they can be printed from within the function. If the
numbers have to be used in a calculation, then the calculation can be done inside the
function and only the result passed back. As it happens, there are ways, but they depend on
these mysterious pointers that you keep hearing about. Perhaps it’s time we took a look at
these.
3-15
Chapter 4
Pointers.
A pointer is a type of number variable, and the reason for its name is that it ‘points’ to where
something is stored. For example, suppose that you have the character ‘C’ stored in the
memory of the Amstrad. What this actually means is that one of the memory cells is storing
the number which is the ASCII code for ‘C’, the number 67. Now memory for a computer is
organised so that each unit (or byte) is numbered, and we might know that the number of
the byte which held our ‘C’ was 41967. This number of41967, then, is the number which is
the pointer for ‘C’. We could, if we liked, store the number 41967 somewhere so as to make
it possible for the computer to find where ‘C’ was stored, and this is precisely what the
action of a pointer is. It would be rather a waste to store a pointer for every character, but we
don’t need to. All we need to do is to store a pointer to the start of any variable. Once we
know where the start is, we can locate it and read the required number ofbytes of data. This
is something that is used a lot in assembly language programming, but seldom occurs in
BASIC. A few machines, notably the MSX machines, have a BASIC function called
VARPTR which comes back with a number that tells you where about in the memory a
variable is stored. The Amstrad machines achieve the same effect with the use of @
preceding a variable name. In BASIC, however, there is little use for this action, and not
many programmers make use of it, or are even aware of it. In ‘C’, however, pointers are a
way of storing variables and getting access to them. This is not just a useful feature of‘C’,
it’s something that is central to the way that the language is constructed. Without pointers,
you simply don’t get very far with ‘C’.
Take a look at a simple example just to get the taste of all this.
rna i n ()
char *p,ccr;
ccr= CT ;
p=&ccr;
pri n t+("\n%c“,*p);
printt ( “ \n7.d “ , p) ;
Using a pointer, in this example to point to a character. The pointer number must be declared and
assigned.
FIGURE 4.1
4-1
Figure 4.1 is a program which declares two variables of type char. One of these variables is
ccr, which is a straightforward variable name. The other, however, is referred to as *p.
Now the asterisk, in this context, means ‘contents of. What it implies is that p is an address
in the memory, and a character can be held at that address. The program assigns the
variable ccr with ‘C’, a single ASCII code, and then assigns the pointer by using p=&ccr.
The & operator, used in this context, means ‘address of. The effect, then, is to store the
character ‘C’ at the address held in the pointer variable p. The program then prints out the
character, in the form *p, and the pointer value itself, which when I ran it on my machine
gave -23569. Don’t worry about the negative sign, it only arises from the way that numbers
are converted. The actual address number that this corresponds to is 65536-23569, which
is 41967. You would have seen this number printed if you had replaced %d by %u in the
printf line.
A pointer in ‘C’ is a variable quantity which is the address of another variable. What makes
the pointer valuable is that if the pointer is declared, the compiler does not need to have the
other variable declared. For example, if the pointer to a real number is known, and is
variable p, then the name of the real number does not have to appear earlier in the program.
A pointer reserves space for a declared type of variable, what you put into the space later is
your own business, provided that it’s the correct variable type. In addition, the pointer is a
number (unsigned), but what it points to can be any type of variable, simple (such as
another integer) or structured (like a record which consists of a number of different types).
We can then juggle with the pointers rather than with the variables themselves.
All of this sounds rather academic, so take a look at an example which reveals a little of what
all this is about.
main ()
r
t-
int x,y;
u — £=; *
A -xJ ?
power <x , &y t ;
printf(“\n %d %d“,x,y);
j
power(x,p)
int *p;
*p = X *X*X |
Using a pointer to return a value from a function.
FIGURE 4.2
In Figure 4.2, two integers x and y are declared in the main program. The variable x is
assigned with a value, but y is not. The function power(x,&y) is then called. The
quantities that have been passed here are the variable x and its value, and the pointer toy.
4-2
The important item here is that we don’t deal with y, simply its address pointer. In the
function, the header declares that the content of pointer p is an integer, but we don’t need to
declare p itself. The value of p will be assigned as the address of y, but all this is implied
rather than declared. We can then make the statement which assigns the contents of
pointer p to the cube of number x, and the function ends there. Now if we had assigned
y=x*x*x, then y, if it had been declared in the header of the function, would have been
assigned this value, but it could not have passed it back unless you used the return
statement. Using a pointer does allow a quantity to be returned in the form of its pointer
address. The main program then prints a value of y, using the pointer address of y, which
has now been changed by the function to give the cube of x.
Now this is strong stuff- the quantity that has been stored in a variable y has been changed
without the need to have a line y=x*x*x, or even a direct reference to &y. All that has been
done is to pass &y, the pointer to y, to the function. This is the way in which a function can
return values to a main program, and it’s a method that is very extensively used in the
library functions. In order to make any substantial use of the library functions, we have to
master this idea of using pointers. At the moment, one problem that has been hanging over
us is how to enter numbers, so this seems a good time to introduce one way, the scanf
function. This function is built into HiSoft C, so that we don’t need to use the library to load
it when we compile.
Now as it happens, scanf is not the easiest of functions to use, and a lot of programmers
avoid it like the plague. The principles are reasonably straightforward, though, and it’s
principles that we want to look at. Function scanf is set out very much like printf, with a
control section, and a list of the variables that you want to input. So far, it sounds just like
good old BASIC INPUT A,B,C. The important difference is that scanf requires pointers
to variables, not just variable names by themselves. The other thing, the one that causes a
lot of frustration, is that scanf works to strict and rather old-fashioned rules, and can do
the most amazing thing if you don’t understand the rules.
ma inf)
T.
int n,k;
for (n=0;n<=i0;n++>
'K.
printf\ ,- \n Number, please- "
scanf i "/icT/.c" ,&k> ;
k=k * k;
pri ntf i " \n square i s %d“,k >;
Using scanf for the input of a number. The number must be assigned to a pointer.
FIGURE 4.3
4-3
Figure 4.3 illustrates a scanf action, and gives you a taste of its use of pointers and one ofits
peculiarities. The main program sets up a loop which will run from 0 to 10, 11 passes
through the loop. In each loop, we want to print a brief message, input a number, calculate
its square, and then print that value. The input of the number is the action that is assigned
to scanf, and the syntax for this particular example is
scanf(“%d%c”,&k);
which at first sight looks rather baffling. Run it, and check that it does as it ought to,
remembering that all the arithmetic is integer, so that squaring large numbers will give very
peculiar results. On the whole, it does as you might expect, though you’ll notice that there
has been an extra blank line. This is because of the use of (ENTER) to terminate the scanf
line. You can miss out the \ n portion of the second printf statement if you want to close it
all up. So far, so good, but what’s the %c for in the scanf statement? You’ll see if you try the
program with this removed. The first number is accepted, but following that one, the loop
cycles round without waiting for you to enter anything! The reason is that you used the
(ENTER) key after typing the number. Without the %c in the scanf specification, the
(ENTER) character is stored and used each time scanf comes round. Since you don’t have
time to enter a number, this isn’t done. Unless you are working with a loop, this action is of
no importance, and a lot of books on ‘C’ don’t even mention it. By adding the %c into the
‘specifier’ part of scanf, you allow for the (ENTER) character, and the loop works
correctly. There is another way of getting round the problem, which is to leave a space
ahead of the %d specifier, and we’ll illustrate that method later.
There are a lot of possibilities here, but the important point to look at is how scanf deals
with the address pointer to variable k. The quantity that is called for in scanf is &k, the
pointer to k. The action of scanf is to assign the number that you type into this pointer, so
that the variable k can be used with this value. It’s a very good illustration of a function
being used to work with a pointer, and scanf is a function which requires that all of its
returned values should be pointers. We’ll come back to scanf later, but for the moment try
editing the scanf line so that the specifier part reads “%d%c%*c”. The %*c part makes the
scanf action skip a character, and its effect in this case is to allow you to enter a number,
but to hang up when you press (RETURN), and wait until you press (RETURN) again.
Arrays.
In BASIC, you have simple variables, such as integers, reals and strings; and you also have
one structured variable, the array. By ‘structured’, I mean that an array name like A means
not just a single value, but a set of values which carry distinguishing names like A(l), A(2)
and so on. ‘C’ is very well equipped with ‘structured’ variables, or structured data types, as
they are called. One of these types is the array, and for a number of reasons we need now to
look at what it is and what we can achieve with it. As you might expect, an array has to be
declared at the start of a program and this declaration will include a name (the identifier),
the number of elements in the array, and the type of data that is to be stored. This is just
4-4
what you would expect from our experience of arrays in BASIC. When you use a DIM
statement, such as DIM Name$(20), in BASIC, you are specifying the name Name, the
type (string) and the number of elements (from 0 to 20, a total of21). The main difference in
‘C’ then will be the way in which an array is defined rather than the information which is
used.
Suppose, for example, that we want an array called classmarks to hold a set of 20 integer
numbers. The declaration that we need for this looks something like this:
int classmarks [20];
This provides the name of the array, which is classmarks, the number of items (20 of them)
and the fact that each item will be an integer. This variable declaration can be made along
with other declarations of integers in the same line. One important point to note here is the
use of the square brackets. If you forget that you are writing ‘C’ and not BASIC, it’s easy to
refer to an item as classmarks(12), when you should be using classmarks [12] instead. The
error message that you get when you do something like this will not necessarily remind you
of what has gone wrong. Since the array in this example is an array of integers, you can use
scanf to get each item of the array. One very important difference between the BASIC
array and the ‘C’ array, however, lies in the way that items are numbered. When you define
a BASIC array as A(20), then this allows for 21 items, A(0) to A(20). By contrast, the ‘C’
array allows for just the 20 that you specified, and these will be [0] to [19], there will be no
[20]. This might not stop you trying to use an item [20], and this is one of the things that you
have to be very careful about because ‘C’ doesn’t always stop you from doing foolish things,
it lets you go ahead and pour garbage into the memory which you don’t find until later!
mai n ()
L
int nun),c 1 assmarksL281;
tor(num=0;num<=19;num++)
r
printt<“\nMark %a - ”,num+l>;
scant (""/.2tiXc" ,&cl assmar ksCnuml) ;
1
j
pnntf ( “ \t \nV.25s\n “ , "CLASSMARKS") ;
tor (nui7i=0; num< = 19; num++)
tprintt ( " \nltem V.d got '/.a marks" ,num+i ,
classmarkstnuml);
-k
/
Using an integer array. The array numbers are read in from the keyboard, and then printed.
FIGURE 4.4
4-5
Figure 4.4 shows an example of this array in use, and also some more formatting. In this
program, a set of twenty marks is obtained. There’s nothing to stop you from entering
numbers like 5000, but the program assumes that the items will be between 0 and 99. Later
on, we’ll see that this can be checked in better ways. The items are entered in a loop, using
principles that should be familiar by now. The prompt line uses num+1 rather than num
so that you can have numbers from 1 to 20 instead ofO to 19. In the scanf line, the %2d
allocates the numbers in twos. This is not ideal, because it means that if you type a four-
figure number, it will be allocated to two sets of marks! For the moment, though, it will
serve to keep the numbers below 99. The other point about the use of scanf here is that the
array pointer is used directly, as &classmarks[num]. You don’t have to use any
intermediate integer here, as you are required to do in some languages.
When all of the items have been entered, the screen clears and the title CLASSMARKS is
printed in the centre. This is done in the printf line by using the control string
“ \ f \ n%25s \ n”. The \ f part clears the screen, and the \ n part takes a line down. The
next part, the %25s, is a string ‘field size’ number for the word CLASSMARKS. The field
size number represents the total size of string that is printed, and if the word is less than this
size, it is padded with blanks at the left hand side (in other words, it is right justified). By
using a positive number, we force the word to be printed with any of these padding blanks
on the left-hand side. The choice of the number 25 with CLASSMARKS (which has ten
letters) means that 15 spaces will be printed to the left of the name. Figure 4.5 shows how
this can be used to centre any name.
1. Count the number of letters in the title, for example, 16.
2. Add this number to the number of screen characters per line, for example 40 in Mode 1
(example gives 56).
3. Divide this number by two, and use it in the field size. For example, “%28s”.
How to centre any title, using the fielding number.
FIGURE 4.5
If, incidentally, a negative ‘field’ number is used, the excess spaces are printed to the right
of the name. This can be useful for spacing the next name, but is not so useful for a heading.
The items of the array are then printed out in order, using the variable name num as the
array number and num+1 as the item number. The printing line has not attempted to
make the spacing such that the lines will be uniform for both single-digit and two-digit
numbers. This, once again, is a good exercise for you in fielding these printouts. If you have
been used to the action ofTAB in BASIC, the use of the field numbers can often be difficult
to adapt to, and the more experience you have the better. The rule is to use a positive field
number when you want to start a word away from the left-hand side of the screen, and a
negative number to space the next item along so that it will fit neatly.
4-6
The method of using scanf with the number specified by %2d makes sure that no number
of more than two digits can be entered. This is not always a desirable method of checking,
however. The main problem is that if your finger slips and you enter 999 instead of 99, then
99 gets entered in one mark, and the last ‘9’ becomes the first digit of the next mark. The
program as it stands gives you no chance to do anything other than grin and bear it. This is a
compiled program, remember, so you can’t just use the old BASIC trick of commanding a
GOTO to get you back into the right part of the program. This method of specification,
then, is suitable only when there is no loop involved, so that it doesn’t matter if something
gets left over. If you want to be able to correct an item without running the program all
over again, then an IF test (like BASIC) is a preferable method, and we’ll look at another
method shortly. There’s no reason why the result should not then be assigned to an array
which has restricted values. The golden rule is that your program should never bomb out
when the unlucky user (you, perhaps) has just entered a lot of data.
Strings at last.
A string in HiSoft C can be regarded as an array of ASCII characters, ending with a zero.
This is true in all other versions of‘C’, (and in some types of BASIC) but in BASIC there is
a ready-made string variable type, marked with the dollar sign, as one of the main variable
types. HiSoft C, in common with other varieties of‘C’, does not have these string variables
ready-made; we have to define them for ourselves. A string is an array of characters and we
always define it in that way, as for example string[20]. When you write characters for a
string in a program you enclose them with quotes just as you would in BASIC, and you
don’t have to type in the zero that is always used to end the string. As an example, which is
also a very important guide to the use of pointers, take a look at Figure 4.6.
main ()
r
static char st L1= ‘EXAMPLE“;
int n;
char *p;
p=st;
for <rt=<3; n<=9; n++>
pr intf ( “ \n'/.c , code %d , is in V.d “ , * (p+n) ,
* tp+n) ,p+n);
1
J
A string variable, and its use with a pointer to print out characters, codes and addresses.
FIGURE 4.6
Now in this example a string is assigned, and the assignment is different from what you
might expect. Both declaration and assignment have been carried out at the same time, and
this is possible only with a static variable in any variety of‘C’. This is not a restriction of
HiSoft C because we are initialising an array, and no version of‘C’ allows an auto array to
be initialised. In addition, though, the number of characters in the string has not been
declared; there is no number between the square brackets. This is something that can be
done only in a combined declaration and assignment, and you can’t split this into two
statements like char st[]; st[] =“EXAMPLE”.
4-7
The real meat of this example, however, lies in the use of a pointer defined as p. Now in the
6th fine of the program, we make the assignment p=st, which looks very peculiar. It rather
looks as if we are assigning a pointer, which is an address number, to the name for an array.
You would expect by now that an assignment of this kind should be written as p=&st[0],
and it would make not the slightest difference to the way that the program works if you did
so. This is another of these short-cuts of‘C’. The name of any array (and a few other
compound data types, as we’ll see) is the pointer address of the first item in the array. The
array is stored in consecutive addreses in the memory, as the example shows, and the use of
pointers is particularly handy just because of that. You don’t, of course, have to make use of
pointers if you just want to print a string or select one item from it. Y ou can print a string by
using a library routine, knowing, as you do now, that the pointer for the string is
represented by its name. You can pick a letter from the string by using the fact that it is an
array, so that st[4] is the fifth letter (the count starts at 0, remember). Later, we’ll see that it
is possible to assign and use a string using only the pointer, with no string variable name at
all.
ilia ini)
static char stCj="EXAMPLE";
printf (" \nV.s “ ,st) ;
print-f (“\n Fifth character is Xc",stC43)
Picking out a character from a string, using its array number. This is much simpler than MID$ in BASIC.
FIGURE 4.7
Figure 4.7 shows string selection more clearly. Once again the string is initialised, and the
complete string is printed using a printf line. Only the array name, st, needs to be used
here, and no square brackets are needed. In the following line, the fifth character in the
string is printed by using st[4] - remember once again that counting starts with zero. Using
the idea that each letter in an array can be located by using its subscript number, you can
always assign one array to another variable name. For example if you have variables st and
string, both of which are character array types, you can use a routine such as is shown in
Figure 4.8 to make a copy of the array st into the array name string.
main t)
static char stLj=“Sampie String";
char stringt2(2J;
l nt n;
for (n=0;n<=i9;n++)
string Cn3=st En1;
printf (" \n name is %s‘‘,string) ;
4-8
Copying one string to another - you can’t equate the string names as you do in BASIC.
FIGURE 4.8
This is pretty much the same method as you would use in BASIC to copy one array into
another. As it happens, you have a more efficient method available in the form of the
strcpy function in the library.
ttmclude stdio.h
main()
r
static char stL3="Sampie String";
char stringL2B3;
strcpy <string,st>;
print-f("\n name is 7.s“ ,string) ;
•k
J
#include stdio.iib
Using the library function strcpy.
FIGURE 4.9
Figure 4.9 shows how this can be included into a routine that needs a lot of library
functions. Remember that if you need just one library function, it’s much easier just to type
the source code of the function that you want into your listing than to have to wait for the
library to be read each time you compile. The library version makes use of pointers, as you
might expect, and it’s a good example of just how compact ‘C’ code can be made.
Let’s get back to the strings, however.
ma i n ()
i
char c,nameC203;
static int n-0;
do
c=getcnar<);
nameLnJ=c;
n=n+1;
whi 1 e (c i = ' \n ) ;
nanteLn-1 3=0;
n=0;
while (nameCn]!=0)
putchar(nameLnd);
n=n+1;
Reading and writing a string character by character. This makes it easy to test for a ‘terminator’.
FIGURE 4.10
4-9
Figure 4.10 shows a very simple string variable reading and writing program. This time,
very different methods have been used for entering and writing the name. We have made
use of some of the simple built-in functions that exist for reading and writing characters,
and this has involved two different types of loop, the while and the do..while. The word
name is defined as an array of 20 items of type char. This means that name should not
contain more than 20 characters, but there is nothing to stop you from entering more than
20. This is a feature of‘C’, that you have to build in your own safeguards, the language
provides only the minimum necessary. If you enter only two characters from the keyboard
no harm is done, but the rest of the array will be filled with garbage. If you enter 22
characters, the program may crash at a later stage. After the declaration and initialisation
steps, the program starts a loop. This loop is not the kind that is regulated by a counting
number, like the for type of loop. Instead, the actions that follow the keyword do are
repeated until a condition that follows while (at the end of the loop) is TRUE. It’s rather
like the WHILE...WEND loop of the Amstrad turned the other way round. The test is
made at the end of the loop, so that the loop must run at least once. The statements in the
loop are enclosed in curly brackets, so that they act as one ‘compound’ statement. The
c=getchar() function does what its name suggests, it gets a character from the keyboard.
The next line assigns this character a place in the string, name, and following that, the
counter integer n is incremented- I have used n=n+l, but you know a better method,
don’t you? The while condition is for the character to be the carriage return, indicated as
‘ \ n’ - and note the apostrophe signs, not quotes, because this is a single character, not a
string with a zero at the end.
So far, so good. Everything you enter at the keyboard will be taken and put into the array
name. Everything, that is, including the \ n at the end, but with no final zero, because we
can’t type a zero character (the Oon the keyboard is ASCII 48, not ASCII 0). The statement
name[n-l]=0 remedies this problem by stepping back to the \n character and
substituting a zero. Now we can print the string. You could, of course, make useofprintf to
do this, but since we’re looking at the simple functions, let’s use putchar. This, like
getchar, works on one character at a time. Also like getchar, it has to be instructed rather
closely, and we have to start by setting the counter n to zero again, otherwise we can’t print
the correct characters. Now we use a while loop. Unlike the WHILE...WEND in Amstrad
BASIC, the while loop in ‘C’ carries out a set of instructions which either end with a
semicolon, or are enclosed by curly brackets. In this example, we’ve used the curly brackets,
so that providing that name[n] is not a zero, putchar will place the character on the
screen, and the number n will be incremented. When the zero is found, the action stops.
Now it works, but no seasoned ‘C’ programmer could possibly be happy with it. The whole
principle of‘C’ is that you ought to be able to do a lot with a very few instructions. In an
example like this, it’s preferable to make use of some of the library routines which use
pointers, but just for the exercise, how could we go about slimming this down? The answer
is to make use of a few of the ‘C’ shortcuts, and some of these are illustrated in Figure 4.11.
4-10
main ()
{
char n a<ne L 263 3 ;
static int n=0;
whilet(name[n++]=getchar<) ) i= \n')$
nametn3=0;
n=0;
while (nametn3!=0)
putchar(nametn++3 >;
The program of Figure 4.10 slimmed down or compacted.
FIGURE 4.11
This is the same program, using the same routines and methods, but with shortcuts. It takes
ten lines instead of nineteen to write, so the slimming, while not exactly anorexia
computerosa, is impressive. The main reduction is obtained by using the line:
while((name[n++]=getchar())!=‘ \ n’);
which is the sort of thing that gives ‘C’ a bad reputation with academics. When you unravel
it it’s not quite so bad as it looks, and you’ll soon learn to shrink lines down to this state. The
way to unravel anything like this is to start at the innermost brackets. Within these you’ll
find: name[n++]=getchar() and this assigns the character from the keyboard to
name[n], and then increments n. This allows us to remove the n=n+l fine from the old
program, and also replaces the clumsy assignment to char c and then to name[n]. This
complete part of the statement is enclosed in brackets, following which is !=‘ \ n’, testing
to find if what is within the brackets is not equal to the (ENTER) key code. The whole of
this is enclosed in brackets which follow the while. This has the effect of testing if the whole
expression is TRUE or FALSE. If the key is not (ENTER), the expression is TRUE, and
the while loop is carried out. In other words, the character is put into the string and the
place number is incremented. When the \ n code is found, the expression which follows
while is FALSE, and that’s the end of the loop. The whole of this while loop is in one line,
marked by the semicolon at the end of the line. The two actions which follow are as in the
old version, putting a zero at the end of the name and making n equal to zero again. Using
name[n]=0 actually causes the string to include a \ n character, but the effect of using
name[n-l] doesn’t make much difference until the printout stage when it changes the
number of spaces under the printed version. We can, in fact, save another line here by
using:
n=name[n]=0;
which carries out the two assignments to zero in one step. Finally, the putchar part of the
work is done in two lines with another while loop, and you should be able to unravel that
one from your experience of the getchar loop.
4-11
In HiSoft C, string actions look rather complicated because assignment is not quite so easy
as you might think. Since a string is a form of array, and there’s no method by which you
can copy one array into another except character by character, it all looks like hard work.
It’s really only a problem, however, if you are ‘thinking in BASIC’. You can assign a string
constant, for example, by using ^define in a lot ofplaces where in BASIC you would use a
string variable. You can also use the scheme that we looked at earlier, of defining a static
char string[] into which you can assign anything you want. You can also make a pointer
point to any string you want, which is probably the easiest way of re-assigning a string
name. We don’t need to go into this, because it’s in the HiSoft C library (it’s one that exists
in every ‘C’ library). When you really need to use a string variable is when you are inputting
or outputting strings, and once again there is a library routine, gets(string), for this
purpose. We’ll look at these routines later. The thing that you really have to be careful
about is any attempt to assign a string to an array which is not large enough, because only a
string of at least the same length is compatible. If your strings are arrays of 20 characters,
then only another array of up to 20 characters total (including zero or any \ n character)
can be assigned. This is very difficult to get used to when you have been accustomed to the
free and easy ways that BASIC has with strings, and Chapter 8 of this book is devoted to
ways of making life easier for you.
Arrays of strings.
In BASIC, you are accustomed to being able to use string arrays, with assignments such as
A$(5)=“FRIDAY”. In HiSoft C, a string array is an array of an array of characters. This
sounds complicated until you realise that it’s no more than a two-dimensional array. The
sort of thing which in BASIC is written as A(3,4) is treated very similarly in HiSoft C, with
the minor difference in this case that one of the dimensions is a set of ASCII codes. An
easier method is to define a string as an array of characters, and then define another name as
an array of strings. Once you have defined your ‘string array’ name correctly, you can use
the string array rather as you do in BASIC. You must remember, however, that the rules
are rather more strict. Each name in the array, for example, will consist of not more than
the declared number of characters, and it’s likely that anything following the zero that
marks the end of a string will be garbage.
4-12
Look for example at Figure 4.12.
main ()
r
char nauieC i3 J L20 J ;
int n , j ;
tor (n=C;n<=9;n++)
r
\.
j=0;
pnntt i"\n Name please - " > ;
whi1e((nameIn J C j ++l=getchar()) ! = '\n');
nameCn] C j — 1 3=3;
1
J
print-f < **\-F ") ;
tor (n=<3; n<=9; n++)
printf("\nXs“,nameLn 3 >;
Using a string array, a two-dimensional array of characters.
FIGURE 4.12
This consists of a program which will fill an array with names (of up to 20 characters), clear
the screen, and then print the lot out. We start by defining name as an array of ten strings,
each of which is an array of characters, up to 20 characters long. Two integers, n and j, are
then defined to be used as counters. In the first loop, using n=0 to n<=9 because the array
elements are 0 to 9 not 1 to 10, the array name is filled with names that you type from the
keyboard. Each string is referred to by its two numbers, the place in the array of strings, and
the character in each string. For example, name[2][4] means character 4 in string 2,
remembering that character 4 is the fifth character, and string 2 is the third string. The
screen is then cleared, and the array of strings is printed on the screen by using the other
loop. Each name in the array is obtained, once again, by using its position number within
square brackets; name[7] in HiSoft C is equivalent to NAME$(7) in BASIC. Note that
this time you don’t have to use two sets of square brackets. By specifying that you want to
print a string, you have automatically made it unnecessary to specify the second set of
square brackets. Just as you could use printf to print a string called title, which was
defined as title[25], you can use printf to print a string which is called, for example,
name [4].
A lot of BASIC programs depend on filling an array with values which are taken from an
internal list. You might in BASIC, for example, want to fill an array WEEKS with names
taken from a DATA list of weekdays, such as:
20 FOR N=1 TO 5
30 READ WEEK$(N): NEXT
so that any day can be found by use of the array number. This is not quite so easy in HiSoft
C, because in ‘C’ there is no direct equivalent of the BASIC READ and DATA
4-13
instructions. These instructions are very wasteful of memory, because everything that you
have in a DATA line in BASIC is stored in two places while the program is running. The
action of READ....DATA in BASIC is really just the initialisation of an array in ‘C’, and the
program ofFigure 4.13 illustrates this action in a form of a function which you can use for
your own programs.
rnai n < >
r
static char *week C 3= C"Monday Tuesday,
“Wednesday“,"Thursday“,"Friday"3 ;
i nt n;
tor (n=B; n<=4;n++>
print!i"\n Day %d is Xs" ,n+1,weekCn J> ;
Initialising an array, the equivalent of using READ..DATA in BASIC.
FIGURE 4.13
This time the array is an array of pointers, one pointer for each string, with no restriction
on string length; a subject that we’ll come back to. The name of the pointer is week[], it
points to a character type, and its initilisation is carried out as shown. The storage class
must be static if we are to carry out declaration and initialisation in one line, and the new
feature is how a set of words, between quotes, can be put into an array. The contents of the
array are shown between curly brackets, separated by commas. In an initialisation there is
no need to show the number between the square brackets of the name, so this goes in as
*week[], When the array of strings is printed out, we don’t print *week[0], *week[l] and
so on, but week[0], week[l] etc. This is because the pointer name is the name of the array
item. This is the kind of thing that’s always likely to catch you out when you first start
writing programs in ‘C’, and it’s the first thing to suspect if you find that a printout gives
you a screen full of gibberish. Note, incidentally, that each string will end correctly with a
zero. You haven’t put this in, but it’s taken for granted when you use letters between
quotes, like “Monday”.
So far, we have been looking at comparatively short programs. When your programs get to
the length at which they take up more than one screen ‘page’, a printer becomes a more
pressing necessity. It’s particularly useful if you are using pointers and you are not sure
whether you should be using a *x or just x at any particular time. If you can see the
declarations at the same time as you look at the lines that are giving you the problems, it all
becomes much easier. Another problem is that by the nature of‘C’, you tend to have a lot of
nested {and} marks. If you can see these only on the screen, it’s very difficult to be sure that
each { corresponds to the correct}. Of course, if you planned the program correctly in the
first place, you will have checked the nesting on paper. The problem arises, however, when
you have been doing some editing, renumbering the lines, correcting mistakes and so on. At
that stage, checking for an incorrectly placed } on the screen alone can be rather a
frustrating task. One thing that can make a ‘C’ program much easier to read is indenting
each new { or loop. In this way, sections which are ‘compound statements’, running as if
they consisted of one single instruction, are set away from the left-hand side in a block.
This makes it easier to see where the { and 1 of each block is located.
4-14
Chapter 5
Menus, choices and files.
The BASIC of the Amstrad allows the simplest method of programming menus, using the
ON K GOSUB type of command. Since a menu is a very common feature of a lot of
programs, it’s time that we took a look at how such a system can be programmed in HiSoft
C. The key to simple menu programming is the switch statement, which is ‘C’s equivalent
of ON K GOSUB. Suppose that you have a list of items on your menu, with each item
numbered in the usual way. You then use a keyboard read function to input a number.
Suppose, for example, that you use the getchar function, which is built-in. You can then
program:
switch(getchar()-48)
!
case 0:(first action);
break;
case l:(second action);
break;
and so on, with the closing curly bracket at the end of the list. The function getchar() will
have values which are ASCII codes for numbers such as 0,1,2, and so on, so that
getchar()-48 converts to number form as we saw earlier. This allows switch to select the
command which appears after the same number in the list that follows case. In a real
program, each of these options would consist of a function name, or a statement. For an
illustration, we can substitute simple printf statements, as in Figure 5.1. The important
point is to understand why the break statement has been added in each line.
5-1
main (>
l
printf <" \f%245\n" , "THE MENU");
printf("\n 1. Start file.");
printf (“\n 2. Add to -file.");
printf("\n 3. Delete item.");
printf(“\n 4. Amend item.");
printf("\n 5. End program.\n");
switch (getcharO-48)
f
X
case i:printf("\n file starts nere.“);
break;
case 2;pnntf(“\n add to file here.");
break;
case 3:printf(“\n delete item here.");
break;
case 4:pnntf(“\n amend item here.");
break;
case 5:printf("\n end of program.");
break;
default:printf(“\n No such item- please
try again.");
A skeleton menu program, showing how the switch statement is used.
FIGURE 5.1
In this example, the screen is cleared by the \ f part of the first printf statment. This prints
a title, and the fielding command %24s has been used. Note that when anything like this is
done, the message must be separated by a comma from the fielding. If you use
printf(“\ f%24s\ n THE MENU”) ; you will see a set of gibberish characters appear
preceding THE MENU. The menu items are then printed, with a number allocated to each
item. You are asked to choose by number, and then your number choice is put into the
switch statement. What follows lists the numbers and actions. Each number is followed by
a colon, then the action or actions that must be carried out. The set of switch actions must
end with a closing curly bracket, and the whole program ends as usual with the final curly
bracket. Each choice has simply caused a phrase to be printed in this example, because the
aim is just to show what the switch statement does and how it is programmed. To see why
we need the break statements, try omitting one or two. You’ll see that this has the effect of
allowing more than one answer to be printed. The switch statement allows you to select
one item, but when the action returns, it will move to the next case statement. Unless you
want the next switch line to be carried out, you must make this next statement the break
to allow the rest of the switch sections to be skipped. Notice, too, that we can cater for a
selection which is not in the range that switch allows. This is done by the default item,
and it’s a very handy way of ensuring that the entry range is checked and something
sensible done for each possible answer.
5-2
In many examples, though, that’s still not quite what we want. What we would like is to
have the menu repeated until we enter a number that is suitable. In primitive varieties of
BASIC you have to use a GOTO to achieve this, but in ‘C’, the obvious way is to use the
do..while instruction. This is illustrated in Figure 5.2, in which the selection is repeated
until a choice in the correct range is made.
mai ri <)
l.
i nt j ;
printf ( "\f °/.24s\n " , "THE MENU");
printf("\n 1. Start tile.");
printf<"\n 2. Add to file. 11 );
printf("\n 3. Delete item.");
printft"\n 4. Amend item.");
printf("\n 5. End program.\n");
do
r
switch i j=geicnar()-48)
t
case 1:printf(“\n file starts here.")
5
break;
case 2:printf(“\n add to file here.")
J
break;
case 3:printf<"\n delete item here.")
break;
case 4:printf(“\n amend item here.");
break;
case 5: printf<"\n end of program.");
break;
case —38:break;
default: printf ( "\n No such item-'/.d p
1 ease try again.\n“ , j);
J
J
wh i 1 e ( j >5 ! J j < i ) ;
Repeating the switch action until a number in the correct range is entered.
FIGURE 5.2
This time, the program does not end when an incorrect choice is made. The message is
printed by the function, and the do loop ensures that the choice can be made again until the
number lies in the correct range. It’s not quite so straightforward as it seems, however. If
5-3
you simply add a do..while loop, you need a quantity to test at the end, and this can be
obtained by using a variable to store the value obtained from getchar. In the example of
Figure 5.2, the integer j has been used. You don’t need to equate this to getchar() in a
separate line, it can all be contained within the switch statement as the example shows. At
the end of the loop, the value of j is tested. This contains the statement (j>5 | | j<l), and
the novelty here is the vertical bar signs. Used in this way, in pairs, they mean logical OR,
so that the statement in brackets tests the truth of‘j greater then 5 OR j less than T.
The trouble is that this always causes the default message to be issued twice, once for the
incorrect number, and once again for the (ENTER) key. This is because entry from the
keyboard is done by storing the characters in a memory buffer. The (ENTER) key returns
ASCII 10, and 10-48 gives-38, so this is assigned to j after the first default message, causing
another message - but there are no more characters left now in the buffer. Now this could
be sorted out by a bit of machine code which clears out (or flushes) the buffer, but there is a
simpler ‘all-C’ solution, which is to make a case -38:break; to detect this and ignore it.
This is one of the delightful things about ‘C’, that there is so often a way out of difficulties
which doesn’t involve digging into the machine-code. This is important, because the
Amstrad machines are not so compatible with each other as you might think, and it’s
always best to avoid machine-code unless you are certain that it works on the machine that
you are using.
Incidentally, now that we’re starting to look at programs which contain several sets of
nested curly brackets, it’s time to think of indenting program lines. Indenting means
leaving a space at the left-hand side, and it’s a good way of showing how statements within
curly brackets are nested. If you have nested sets of curly brackets in a program, put each
new starting bracket one space in from the previous one, and indent all of the statements
within the brackets similarly. This makes it much easier to read the program and see which
statements are included in which set of brackets. The listing for Figure 5.2 should give you
some flavour of all this. The semicolons at the end of the switch lines have spilled over
because the printer has been set to use the same 40-character lines as the screen.
Other cases.
The control for switch does not have to be defined as an integer because, as we have seen
previously, a character is entered as an ASCII code which is an integer anyhow. You can,
therefore, use a character to control a switch, as is illustrated in Figure 5.3.
5-4
main <)
char s;
s= S';
do
"i.
it (s!= (2 >3= getchar () ;
printf(“\ntype a ietter\n“>;
s^getcharO;
switch(a)
case a':
case e':
case i i
case o :
case u :printf<" -is a vowel\n">;
break;
def aul t: pr i ntf ( “ --is a consonant\n " ) ;
whlie (s!= @');
Controlling switch with a character rather than with a number.
FIGURE 5.3
In this example, the variable s is of type char, meaning a letter, and the switch statements
are set up for letter testing. We can still use s=getchr() to get the character from the
keyboard, however. This is because, once again, the language does not make any rigid
division between characters and integers - the main difference as far as the computer is
concerned is that a character is stored in only one byte of memory, and an integer requires
two bytes. Note that a character is referred to by using its key, within single quotes, such as
‘S’, ‘A’ and so on. This is something you constantly have to remember, because using
double quotes, such as “S”, “A”, means a string which consists of the letter code and a zero.
Once again, in this program, the use of a do..while loop will cause problems, in particular
the repetition of the ‘type a letter’ message. This time, the method that has been used to
prevent this is different. It is certainly possible to use an extra case line to detect the
character ‘ \ n’, but this does not prevent the prompt from being printed twice. The test at
the start of the loop, however, along with the assignment to ‘@’ before the start of the loop
does what is needed. Assigning the value ‘@’ to s before the loop starts prevents the extra
s=getchar() step from being used. If the loop returns because the @ key has not been
pressed, the value of s cannot be @, so the extra getchar will read the (ENTER) code of
‘ \ n’, and allow the program to operate normally.
This type of character input can be improved by using some of the built-in library
functions, and one of these can also be used to get over the (ENTER) key difficulties. The
improved program is shown in Figure 5.4.
5-5
main i)
I
cnar 5 ;
print+("\nType a letter - use & to atop\
II / l
whi1e t < 5=getchar<)) != e')
r
v
l-f ii5spaceis) ) continue;
it (! isalpha (s>)
printt ("not a letterin' 1 ) ;
continue;
1
J
tolower(s);
5W1tch(s)
r
t
case a ’ :
case 'e':
case i :
case 'o :
case u: printt 4“ -is a vowel \n‘‘);
break;
detault:printf (“ -is a consonant\n l- ) ;
Analysing letters with a switch statement and some of the built-in character actions.
FIGURE 5.4
This time, the test for escaping from the loop (the @ character) is made at the start, using a
while loop. You have to be careful how this is done, with the s=getchar() step enclosed in
brackets and made not equal to ‘@’, and the whole expression in brackets for the while
statement. If you get these brackets wrong, such as by using while(s=getchar()!=’@’)
then you will find that s is not assigned to any character that is not @, which is not exactly
what you wanted. In the loop, two tests are then made. The first test uses the isspace
function, which is TRUE if s happens to be a space, the newline character or a TAB. In this
example, it’s the newline we are trapping, and the effect will be to continue if the character
is a newline. ‘Continue’ used in any type of loop means that the rest of the loop will be
skipped, and the loop is restarted. If the newline is found, then, the loop returns for another
getchar. The next test uses function isalpha. By using this in the form if (! isalpha(s)),
we get a TRUE answer if the character is not alphabetical. For this event, we print out the
‘not a letter’ message, and continue to get another letter. If character s has survived so far,
we then use function tolower(s) so that any upper-case letter is converted to lower case.
This avoids the problem of entering an upper-case letter like A, E, I, 0 ,U and being told
that each is a consonant. All of these functions are built in, and don’t need the library to be
searched.
5-6
One last point about switch. The expression that follows switch, within brackets, must
give a single integer. You can’t for example, make switch work with strings, except to
recognise the first character of a string. If you have to work with strings, then a program like
the one in Figure 5.5 will be more suitable.
main()
u
int j,n;
char command 1163;
pnnt-f ("\nPlease type command") ;
pnntf ("\nCLS,UP,DOWN,LEFT,RIGHT") ;
do
t
n=0;
whi1e ( ( j=rawin()) 1=13)
r
commandCn i —j ;
n=n+1;
\
J
command Cn J='\0 '\
if (istrcmp(command,"cis" ) )rawout( \14
> ;
if (istrcmp(command,"up")> rawout('\13'
);
if (!strcmp(command,"down"))rawout( \12
' );rawout( \10 );
if (!strcmp(command,"1ef t"))rawout('\10
' ^ ;
if (‘strcmp(command,"right">)rawout('\1
i' >;
rawout(233);
J'
while (command);
J
int strcmp(s,t)
char *s,*t;
r
while (*s==*t)
/
V.
if (!*s) return 0;
++s;++t;
J
return *s—*t;
T
J
Using a menu structure to recognise command words.
FIGURE 5.5
5-7
The name command is defined as a string of up to six letters, and it is filled with characters
by using a loop which contains rawin. This requires testing for the ASCII code of 13 to
check the use of the (RETURN)/(ENTER) key. The point of using rawin in this example,
is that it does not place anything on the screen. For some purposes, particularly graphics
programs in which pressing an answer should not show on the screen, this action can be
useful. The command word which is obtained is then compared with a list of keywords by
using if tests with strcmp. There is no way in which you can make a direct comparison of one
string with another in ‘C’, so that lines such as:
if (command==“cls”)...
are not valid. The strcmp function, which is in the function library, does the comparison
character by character, and returns a number whenever two characters are unequal. If the
strings match perfectly, then the function returns 0. We have to test for NOT strcmp,
therefore, using the ! sign. The program example will do things like clear the screen and
move a cursor about by using long commands such as UP, DOWN, LEFT, RIGHT. The
important points are that the words do not appear, and that the comparison can be made.
You will need to press the (ESC) key twice to get out of this one, because the while
condition will make it loop forever.
Recording data.
Once you have made a start to gathering information into arrays, then it’s likely that you’ll
want to record the information on to cassette or disc. The cassette filing system of the
CPC464 follows the same command system as the disc system, and the later 664 and 6128
machines are disc-only, though a cassette system can be plugged in. In this part, then, we’ll
assume disc use, and if you use cassettes the only differences are that you’ll have to choose
places on the cassette and wait around rather a lot. We’ll start at the beginning, and look at
what is involved in recording and replaying a list of integers which will be held in the
computer as an array. Figure 5.6 shows what is involved.
trial n ()
lnt n,aC5iJ;
int *+p;
tor ((1=0; n<=50; n++)
a [ n ] =2-*n;
J
printf <"\nArray fill ed
fp=fopen(“intfi1",“w”> ;
tor <n=0;n<=50;n++)
l.
t printf (f p,"5d\n",a C n J >;
J
fclQ5e<fp);
5-8
A disc or cassette filing program for a list of integers.
FIGURE 5.6
The integers are generated in a loop, which simply gives all the multiples of two up to 100.
Once this array has been generated, the recording file is opened by using the line:
fp=fopen(“intfil”,“w”);
in which fp is a pointer to the start of the file, “intfil” is a filename that will be used on the
disc, and “w” means write. Note that this is “w”, a string, not ‘w 5 , a character. The
function fopen is built-in, so that you don’t need to search the library for it. Once the file
has been opened, the array can be recorded by using another loop, with a variation on
printf being used in the writing process. The function fprintf is used very much like
printf, but with the file-pointer as the first of its arguments. Once again, fprintf is a built-
in function. The whole action could have been carried out in one loop, but I wanted to
separate the generation of the numbers from the filing routine so that it would be easier to
adapt the program for something more useful. The file must be closed by using fclose(fp)
after writing. If there has been any other file called intfil on the disc, it will be renamed as
intfil.bak in the usual way, and the new intfil file will become the current one of that
name. Once the file is on disc, you can look at it, after a fashion, with the type command of
CP/M. This involves losing the ‘C’ compiler to switch into CP/M, and typing type intfil.
You will see the integers appear, very untidily, on the screen with a new line and a right-
space for each new integer. To read the integers back in a more controlled way, we should
write a reading program in ‘C’ - and that’s the next step.
ma inti
int n,bl5lJ,*fp;
fp=+open (“intfil
tor <n=B; n<=50; n++>
r
t.
tscant tf p , "7.d\n‘‘ ,b+n; ;
J
tciose(fp;;
-for tn=0; n<=50; n++)
printf ("Xd ",btn3>;
A reading program for the disc/cassette file.
FIGURE 5.7
One possible reading program is illustrated in Figure 5.7. This one prepares in the usual
way, and opens the file using fp=fopen(“intfil”,“r”) with the “r” (not ‘r’) meaning
“read” in this case. The loop is performed as before, but this time fscanf is used, and the
syntax is not the same as that for fprintf. The reason is that an array is being filled, and
fscanf needs a pointer to the position in the array. Now the name of the array, b, is the
pointer to its first item, b[0], so that if we use b by itself in the scanf instruction, all
numbers will be read into the first item. To make the pointer shift to the correct item, we
use b+n, so that the correct number of address bytes above the pointer start b is being used.
When you add to a pointer in this way, the number that is actually added is a calculated
number, taking into account the type of data. For example, an integer uses two bytes. If
5-9
pointer b happens to be 42000, for example for n=0, then for n=l the address is 42002,
because an integer takes two bytes. This automatic adjustment is very useful, but easily
forgotten. After the numbers have been read, the file is closed in the usual way, and the
array is then printed out. The printout is not in the same format as was used for reading the
array in, which is the main benefit of using a separate loop for this purpose.
The use of fprintf and fscanf is just one of a set of ways of using disc filing.
ttdefine EOF -1
main(>
'i.
char c,aC5il;
int n,j,p;
n=0;
f p=topen ( " n ewe hr " , " w“ ) ;
c = \n ;
whiie <n<=50)
r
it (c ! = '"\n ' )
c=getchar ();
continue;
printt ("\nfype a single character . . “) ;
j=getchar() ;
i t< j== 0 )break;
c=j ;
putc(C,f p )\
n++;
J
f c1ose(f p >;
printt(“\nPress any key to read back");
wh 1 1e(:keyhit ());
n=0;
tp=topeni"newchr“,"r“);
11(f p==0 )
printf<"\nNo such tile");
el se
whi1e((j —getc(4 p >) !—EOF)
v
aLn++J=j;
J
a C n 2 = ' \ 0 ' ;
fclose(tp);
printt("\nXs",a);
j=rawin () ;
Using putc() and getc() in filing programs.
FIGURE 5.8
5-10
Figure 5.8 demonstrates two other functions which can be used, putc() and getc(). As the
names tell you, these are character functions, but this description can be very misleading,
particularly as applied to getc(). The action of getc() is to return an integer, which can, of
course, be regarded as a character in ASCII code. The important point is that you can
assign getc() as an integer or directly as a character, but it’s better always to assign it as an
integer. The reason is that you generally use getc() in a loop which continues until the end-
of-file character is found. The EOF character in HiSoft C is -1, which in integer form
consists of two bytes. If you read getc() as a character, it will only deliver one byte, and the
end of file character cannot be read. That’s usually one fruitful cause of program crashes.
Another is to gather the characters into a string and forget that there must be a zero at the
end when the string is printed!
Looking at the program of Figure 5.8, then, the assignments are made as usual with
character c, string a, and the others integers. As before, the pointer has been defined as
pointer to an integer. The counter n is initialised to zero, and c to the newline character.
This assignment has been made to avoid problems due to the newline trapping in the while
loop that follows. In this loop, the number of characters that can be entered is limited to 51
(from 0 to 50), and a trap for a newline in the keyboard buffer is placed as the first step. After
a letter has been typed, near the end of the loop, this trap will remove the stored newline
character so that the message (‘Type a single character’) is not repeated unnecessarily. If c
is not assigned to a newline before the loop starts, however, the message step will not appear
until the (RETURN)/(ENTER) key is struck. The loop is also arranged to break when the
‘0’ key is used - obviously you could use whatever terminator you wanted, and the message
would normally mention this. Function getchar() is used to get the character from the
keyboard, and the character is extracted the long way round, using integer j, whose value is
tested, and then assigning this to character c. The file which was opened at the start of the
program is then used by putc(c,fp) to place the character c in the file which is pointed to by
fp. This loop continues until a ‘0’ is entered or until the maximum permitted number of
characters has been entered. The file is then closed, and the program hangs up, waiting for
you to press a key.
The ‘press a key’ step is provided by another built-in function, keyhit(). This makes the
program look for any key to be struck, and the keycode remains in a buffer. Using keyhit()
in a while loop causes the program to hang up in a loop, waiting for a key to be hit. When
you press any key, the program then continues, initialising the counter n once again, and
opening the file for reading. Now it can happen that you don’t have the correct disc in the
drive when you are reading a file, and the next part of the program shows how to deal with
this contingency. The pointer fp will be zero if no file exists, so that testing for (fp==0)
allows you to print a message. In a real program, of course, you would want to return to the
waiting step if the disc turned out to be the incorrect one, but in this example, the program
simply stops if the newchr file is not on the disc. If the file is found, then a while loop reads
it until the EOF character is found. The EOF has been defined at the start of the program as
-1, the correct EOF for HiSoft C. We could, of course, have used -1 in place of EOF, but if
you use EOF and ^define, it’s much easier to change a program so as to run on another
machine (or another variety of‘C’). The getcQ function is assigned to the integer j so that
the EOF can be detected, and the conversion to characters is done simply by using
a[n++]=j, in which the character is placed in the array of characters and the place
5-11
number incremented. When the loop ends because of the EOF character, the ‘ \ O’ is added
to make the array into a true string. The file is closed, and the string of characters is printed.
The last step looks mysterious. It reads the key that was struck at the keyhit() step, and
assigns the value to j. The only reason for doing this is to avoid having a code in the
keybuffer when the program ends. If there is a code in the buffer, then the prompt‘T ype y to
run’ appears, and the code in the buffer, unless it is the code for ‘y’, causes a return to the
editor. This is an important point to watch, because these stored key-codes can be a lot of
stored trouble even when a program ends. Now you can start condensing the size of the
program by merging actions as was illustrated previously.
String files.
The use of number and character files is seldom particularly important, except as parts of
other files. That’s something that we shall take a close look at in the following chapter. For
the moment, the important string file is one that we want to attend to. As you know, a string
in ‘C’ is an array of characters which ends with a ‘ \ 0’ marker. An array of strings can be
dealt with in two ways. One is as an array that has two dimensions, such as a[10][10],
another is by keeping an'array of pointers. Experienced ‘C’ programmers work as a matter
of preference with pointers, and we have already had a taste of this when we saw that a
pointer plus a subscript number could be used to refer to an item in an array. In the
following example, we’ll make much more use of pointers by using an array of pointers to
store a string array.
Idetine EOF -1
#define NULL 0
extern char *gets(> ,*fgets() ;
main()
r
*«.
xnt n,*fp;
char strL403,*sp;
n=0;
f p=f open i "strf i I 11 , "w" > ;
do
sp=getsistr >;
tprintf(f p ,"%5\n“,str);
n++;
whi1e (n<=10);
telose <f p> ;
printf<"\nPress any key to read\n");
whlie <!keyhi t () ) ;
f p=fopen < "str+il " , "r" ) ;
if (fp==0>
/
t-
printf("\nNo such file");
5-12
n=2B;
whi1e <sp=fgets(str,n,tp>>
i-f (sp^B) break;
pr 1 nt-f < "7.5“ , sir ) ;
T
J
telose(fp >;
n=rawin (>;
J
char *-gets(s>
char *s;
static int c;
static char *cs;
cs=s;
whi 1 e ( (c=getchar < > > ! =EOF c ! = \n ' ) *c
s++=c;
*cs=B;
return ( (c==-l cs==s) ?NULL: 5) ;
J
char *f gets (s, n , -f p)
char *s;
lnt n;
int *+p;
static int c;
static char *cs;
cs=s;
while (—n>0 S<S< <c=getc (tp) ) ! =EOF> it <
(*c5++=c)=='\n')break;
*cs='\B ;
return ( <c==EOF cs==s) 7NULL: s> ;
Using pointers in a program to store a string array.
FIGURE 5.9
The program is illustrated in Figure 5.9. It’s considerably longer and more complicated
than any of the ‘C’ programs that we have looked at so far, and there are several new points
embedded within it. The first point is that two routines from the library have been included
in the program. So far, when we have included library programs, they have either been the
type which returned an integer (or nothing), or they have been included by using stdio.h
and stdio.lib. This time, two routines which return character pointers have been used, and
placed following the main program. Because they are not simple integer returning routines,
the compiler will need to be notified of them in advance. An alternative is to place the
functions ahead of the main program. In this case, the ‘forward declaration’ is used, with
5-13
extern followed by a copy of the header for each function, gets and fgets. You will find the
two functions in your stdio.lib file, and the forward declarations in stdio.h. The gets()
function will get a string named s from the keyboard, and returns a string pointer which
need not be used. In this example, the pointer has been assigned, but not used, and each
expression could be simplified by omitting the assignment. The only reason for including it
is to show that it can be done.
The first part of the program opens a file called ‘strfil’ which is intended to take a number of
strings, counted by the do..while loop from 0 to 10, eleven in all. Each string is obtained
from the keyboard by using gets(str), a library function which assigns the string to the
name that is supplied and returns a pointer to the first character. The string is then saved to
a disc file by using the usual fprintf routine. Note that this has used str but it could just as
easily have used sp. This is an important point, because it’s easy to assume that you might
need to use *sp, which would, in fact, give just the first character of the string. Once all of
the strings have been read and filed, the file is closed, and the first part of the program ends.
There is no attempt, in an example like this, to check that the number of characters in each
string does not exceed a maximum, and if the dimensioning of str is exceeded, the program
could crash. For a working program, some kind of protection against exceeding the limit
would have to be included.
The replay starts with the ‘Press any key’ type of loop that we encountered earlier. The file
pointer is allocated for a read file, and tested in case the file does not exist. Once again, no
attempt is made to return to the waiting loop at this stage. The file reading loop makes use
of the function fgets. This takes three parameters, str,n,fp, which denote the string,
number of characters and file-pointer respectively. The function will read the file whose
pointer is fp and return a string of n-1 characters from the file. For a reason that we’ll look
at later, it looks as if it doesn’t, but I can assure you that it does. Each string is then printed,
and the printing line used %s to specify a string, but no \ n to force a newline. This is
because the strings already have newline characters when they are put into the file with
fprintf.
So far, so good. For the moment we’ll ignore the library functions, except to point out that
they have to start with a declaration of type, and the asterisk which shows that they each
return a pointer. The program will compile and run as you would expect, but when you test
some of its characteristics, it seems to be misbehaving. In particular, when you enter a
string of more than 20 characters, it appears to be replayed with no alteration. How is this
possible, if the fgets() function is working? The penny drops when you count the number
of strings. If you have put in one long string, you read it back as more than one string. It
appears on the screen undivided because there was no newline character in it when you
entered it. If you really want to see what the reading action does, then place a newline
descriptor of \ n in the printf line for the returned string. You’ll see then that the long
strings are read as strings of up to 19 characters each, this time separated by a newline; with
the other strings separated by two newlines.
5-14
If, like me, you hate to have a mystery left unexplained, I’ll deal with the return line of the
library routines. The test?actionl:action2 line is a way of choosing to return one
quantity or another. If the test is TRUE, then actionl is taken, if the test is FALSE, then
action2 is followed. In the example of *gets(s), the line is:
return((c==-l && cs==s)?NULL:s)
so as to select which quantity to return. The reason for this line is that the function should
return a pointer to NULL, address 0, if for any reasons a true pointer cannot be obtained. In
any other case, the pointer should be s, which is the pointer obtained in the function. The
test is (c==-l && cs==s), meaning that the character is the EOF character (EOF would
have been safer here!) and the pointer to character position, cs, is still pointing where it was
originally set, to s. If this is true, then nothing has been read into the string, and NULL is
returned. For any other values, the pointer s is returned.
5-15
Chapter 6
More structured types.
We have come quite a long way in looking at examples and applications of‘C’, but there are
still plenty of topics to get to grips with. One of these is records, something that is not easy at
the best of times, and more difficult if you have only ever programmed the Amstrad in
BASIC. A record is a collection of items of data, which may all be of the same type or, more
usually, of different types. What makes these items into parts of a record is that they are
related. To take an example, suppose that you wanted to keep a record of membership of the
local Golf Club. You would need the name and the address for each member. These would
be strings, arrays of type char. You might also want to keep year of birth (because Juniors
pay a reduced fee, and senior members pay only green fees), year of joining (members with
ten or twenty years membership have special privilege years), and handicap. All of these
last three items could also be stored as strings, because string entry and storage is easier.
There might also be an entry for fees due (a real number in a real-life program) and
whether paid or not to date.
Now all of this data constitutes a record because for each person, the subject of the record,
all the items belong together. It would not make much sense to keep a file of names, one of
addresses, one of year of birth, and so on, and yet this is the way that we are often forced to
keep such records in BASIC. The alternative in BASIC is often to pack all the data into one
string of set length, and to make up a string array. ‘C’ allows you to define what will go into
a record, and then to create an array of records. Obviously, the ultimate aim of such an
array would be to record it on tape ordisc, but that’s something that we can leave until later.
Another thing that we’ll leave for later is the topic of using ‘pointers’ to locate records in
memory. The type of variable that is used in ‘C’ for a record is called a structure.
#define
EOF -1
#det 1 ne
NULL 0
#detine
total 2
#deflne
true 1
#de+ine
false 0
#detine
N "Name - "
#detlne
A "Address - "
ttde-f i ne
Yb "fear of birth - “
#det i ne
Yd "fear of joining - "
#detlne
H "Handicap - "
#de+ine
S “Subscription in pence
#detlne
P “Paid, Y or N - “
char *gets(s)
char *s;
6-1
static int c;
static char *cs;
cs-5;
whi 1 e ( (c=getcnar <) > ! =£GF c ! = ' \n ) *c
s++=c;
*cs-0;
return s;
J
struct golf_ciub C
char nameC20J;
char address[403;
char birthL5J;
char jointSJ;
char heapL33;
int subs;
char paid;
3 G;
main ()
int j;
char c,sE5J;
tor ij=l;J<=i;j++)
L
rawout(12); /*ciear screen*/
print-f (N) ;
gets (G. name);
prlnttiA;;
gets (G. address;;
prints(VB);
gets(G.uirth);
print-f i V J ) ;
gets <G.join;;
printti H >;
gets(6. heap);
p r i n 1t (3 } ;
gets is;;
G. subs—atoi is);
prlntf(P);
c=getchar t);
toiower<c);
G.paid=c;
print-f i " vnfress an/ key. . . \n'*) ;
j=rawin i);
printf(N);
pr l n 1t i /.s \n ^G.name) ;
printt(A);
printf i 11 3-5\n 11 , G. addr ess3 ;
6-2
printt(YB);
pr i nt + ( “ '/.5\n ",6. birth) ;
pr int-f (YJ > ;
printt t '“/.s\n‘‘ ,G. join) ;
printt<H;;
pr i nt f i " /is\n 11 , G. heap) ;
printt("Subscrlption:- ");
printt ( "#%d. */.d\n“ , <6. subs) / 100, (G. subs)
>.100) ;
prlntt(P>;
printt l “ %c\n“,G.paid);
J
j
atoi(s>
char *s;
static int c,value,sign;
whi led sspace (*s) ) + + s;
value=8;
sign—1;
lt (*s—— — )
r
t
si gn--i;
j
eise it i*s== + ) ++s;
while (i sdigit(c=*s++))value=10*value+c
- 0 ' ;
return sign*vaiue;
The arrangement of a structure, showing how it is declared and used.
FIGURE 6.1
We’ll start by considering what we need to do in Figure 6.1 to declare a structure, using as
an example the Golf Club illustration above. The first action, as usual, is to declare any
constants. In this illustration, we shall make the total membership of the club a constant,
equal to 2, because this is just an example. By making this a small number, you can see how
the program works without wearing out your typing fmger(s). The program which is
illustrated is a long one, and the intention is that you type it once and save it. The
developments can then be added by editing the ‘starter’ program, so that you’ll find a few
items in the starter which aren’t needed immediately. The ^define lines define a number
of useful items, along with a set of messages which will be used in the entry and reprinting
sections of the program. The function char *gets() is then defined, so as to avoid the
business of declaring it as an extern, as we used it previously.
6-3
The important part, however, is what follows in the struct declaration. The name of the
record is given as golf_club. This is a reminder only, because though we could use this as
a variable name, it’s rather unwieldy, as you’ll see. The name that is used here is sometimes
called the ‘tag’ of the structure. The structure golf_club is declared, and what follows
within curly brackets must be a list of the Helds of the record, meaning the items that make
up the record. I have typed these indented, with one item per line, to make them more
obvious. Like any other declaration, the items could be grouped with all the char names
following the char heading, separated by commas. The name and address fields are both
strings, but with different numbers of characters. The two years and the handicap are also
taken as strings, with the dimensioning for five characters in the year because there will be
four digits and the ‘0’ which marks the end of the string. If you don’t dimension adequately,
the program will compile and run, but the results will be decidedly odd! The subscription
amount should be a ‘float’ number- in a program which was seriously intended to keep
records of this type, the amount of the subscription would be calculated from a formula and
printed when required, but in this example, I have made it an entered integer item. The
letter paid is of type char, and will be used for a ‘Y’ or ‘N’ reply, because the subscription
will either be paid or not- this club doesn’t allow instalment payments! The end of the
definition of the fields of this record is marked with the usual} sign. All of this definition
occurs before the start of the main program, and following the curly bracket which ends the
structure definition, we must have a semicolon. If there is only a semicolon, then the name
of the structure can be assigned later by using a line like:
golLclub G;
By using the syntax: }G; , however, we can use G to mean a structure of type golf_club
without using another line, which is much more convenient. We could, if we liked, declare
other names in this way, such as: }G,HJ; so as to mean that G,H,K were all names for
structures of the type golf_club.
The main program then starts, with an integer declared for a counting loop, a character for
the ‘y’ or ‘n’ entry, and a short string for getting the subscription amount. Apart from
scanf, the input functions of‘C’ are biased to character or string entry, so it makes sense to
use string entry for almost everything since the gets() function is being used in any case.
There is a function, atoi which will convert a string of digits into an integer. The program
then starts with a loop. It’s a once only loop in this example, because there’s no point in
using the loop more than once until the program has been expanded a bit, but I’ve put it in
to show how this would be done. In the first section, the screen is cleared, and information is
prompted for and entered. The prompt messages are obtained by using the string constants
N, A, YB... which were defined at the start of the program. The important point to note is
how the inputs are assigned. For the name entry, for example, we use gets(G.name). This
calls function gets(), and assigns the string that it gets from the keyboard to variable
G.name. This is the way that we can select one item (or field) of a record, using the
structure variable name, then a full-stop, then the item name. This syntax lets you assign to
an item in a structure or print an item. Later on, we’ll see that if you want to do anything
more complicated you need, as always, to use pointers.
6-4
The rest of the information is then entered in the same way, with the subscription being
requested in pence. Now this is just a convenience, and it would be just as easy, since a
string is being entered, to enter in conventional pound-dot-pence or pound-dash-pence
form. The parts could then be separated and converted to separate integers to avoid the
problems of not having float numbers. This example is supposed to be a simple one,
however, so we’ll stick to simple ways. The number that is entered is converted to an
integer by function atoi and stored as G.subs. If your subscription is too large to store as an
integer you’re spending too much on golf! The last item is a ‘y’ or ‘n’ entry of a single
character, and the function tolower is used in case the (SHIFT) key was used. The data is
then ‘replayed’, simply to show that it all works. The titles are printed using, mainly, the
string constants, and the answers are obtained from the structure name and portions. Note
that the subscription is printed in pounds and pence by making use of integer division and
modulus operators. The use of j=rawin() for the ‘Press any key’ stage avoids difficulties
with stored characters, and allows the program to end with the usual ‘Type y to run:’ notice.
Filing structures.
The structure in ‘C’ is so useful as a way of packing information into groups that we need
some way of recording structures on disc. It would be pleasant if we had a structure filing
statement which allowed a complete structure to be put on to disc simply by using the
structure name. This, however, can’t be done, and we have to record the items of a
structure one by one. Though this could be done as part of a main program, we’ll learn a lot
more about the use of structures and pointers if we make the structure filing routine part of
a function. The important point here is that you can’t pass the name of a structure to a
function and expect it to do anything about it. You can, however, pass a pointer to a
structure by using the & sign with the structure name. This is needed so often that ‘C’ has a
special way of indicating the items in a structure by way of the pointer. For example, ifsp is
the pointer to a structure, then sp->item will refer to the item in the structure. The ->
sign uses the minus and greater-than signs together.
#det 1 ne
EOF -i
tfdef 1 ne
NULL 0
#define
total 2
#det 1 ne
true 1
#def i ne
False 0
#detine
N “Name — “
#def i ne
A "Address — "
ftdef l ne
Yfe “Year ot birth - "
#de+ine
YJ “Year ot joining -
ii
#de+ine
H “Handicap — “
#detine
S "Subscription in pe
nee - "
#detine
P "Fai d, Y or N —
#define
id 11 vnPrepare disc tor
data\n “
#de+ lne
fi " \nPress any key to
proceed.
#detme MM "NnPress @ key to end, any ot
6-5
her to continue entry.\n“
char *gets<s)
char *s;
r
static int c;
static char *cs;
cs=s;
whiie <(c=getchar())i=£UF && ci= \n )*c
s++—c;
*cs=0;
return s;
J
struct
— 7
golf club t
char
nameL20J;
char
addressL40J
char
birth C5J;
char
join lj];
char
ticapL3J;
char
subsLoJ;
char
i G;
main ()
paidL2J;
int j,count,*fp;
CDunt=0;
printf(W);
printf (h);
j-rawin (>;
fp^fopen i"clubdat“,“w“>;
do
r
t
count++;
rawout(12); /*clear screen*/
printf(N) ;
gets <G.name);
printf(A);
gets(G.address);
printf (YB>;
get5 (G. birth);
printf (YJ ) ;
gets(G.join);
printf (H);
gets <G.heap);
printf(S >s
gets(G.subs);
printf(P )j
gets(G.pai d) ;
filit. if p , &G) ;
6-6
printf(MM>;
j=rawin();
s
whlie (j! = '© ) ;
f close t + p) ;
fp=topen(“dim","w">;
fprintf (f p , “7.d“ , count) ;
telose(fp);
f i 1 i t <f i 1 p , sp)
struct goif_dub *sp;
int *filp;
jr
c
fprintf (flip, "Xs\n‘/,s\n" , sp- >name, sp- >ad
dress;;
f printf (flip, "%5\n7.5\n%s\n" , sp->bi rth , s
p->join,sp->hcap);
f pri ntf < f i i p , M 7.s\n7.s\n" , sp->subs , sp->pa
id);
T
J
Entering data into a structure and recording it on disc.
FIGURE 6.2
A sample structure-filing program is illustrated in Figure 6.2. This has been constructed
out of part of the program ofFigure 6.1, omitting the printing sections. Three new messages
have been added, though one needs tidying up with an additional \ n to force a newline and
prevent a word from being split. A disc file is opened, with the name “clubdat”, for
writing. The setup of the structure is the same as before, as is the entry of information. After
the information on payment of subscriptions has been entered, a function filit() is called to
place the data of the structure on file. This function uses the file pointer for the disc, fp, and
also the pointer address &G for the structure. The program then hangs up waiting for a
reply key to be pressed. If this is the @ key, then the program closes files and terminates.
Any other key will continue the do..while loop. I found that in this type of loop, the
j=rawin() step left something in the buffer which would be taken as a string by gets().
This caused the next cycle of inputs to disregard the ‘Name’ step, and was cured this time
by adding another gets() step following j=rawin(). Note that this was on a CPC464, and
it’s possible that later Amstrad machines might not exhibit this problem.
The meat of the program therefore lies in the function filit, which uses two parameters.
One parameter is the filepointer, filp, an integer, and the other is the structure pointer sp
which is declared as struct golf_club *sp. This declaration is that sp is a pointer to a
structure of type golf_club. The fields of the structure are then sent to the file, using
fprintf statements. The name and address strings are sent first, using “%s%s” as the
specifier for the two strings, and with sp->name , sp->address as the separate fields.
The other fields of the structure are dealt with in the same way, remembering that sp-
>subs is an integer, and sp->paid is a single character. Since the items that are to be
recorded are stored in a buffer until the buffer fills or the file is closed, you don’t necessarily
hear much activity from the disc at the time when this function runs.
6-7
Reading back structures.
Reading back a file of structures from the disc normally uses fscanf, but you must
remember that this function works with pointers. In addition, fscanf will take the end of a
string as being the first ‘white space’ in the string, meaning the first blank or any other
character which does not ‘belong’ in a string, such as the (TAB) key or the (space) key. This
makes fscanf more suited for files of integers, or of strings which can be guaranteed to have
no spaces in them, but it’s not very useful for the type of string that we now want to read,
with names and addresses. Fortunately, the library contains the useful fgets() function,
which is very similar to gets(), but with subtle differences. This function can be used to
read back all of the recorded strings, and will not give trouble if any ‘white space’ is found in
a string.
That doesn’t mean that everything is plain sailing, because when you use a library function
you have to read the small-print (or its listing) to see just what it will do with the data. The
similarity between fgets and gets is close, but one difference is very important. Whereas
gets will read a string of characters, including the (RETURN)/(ENTER) character, and
then replace the (RETURN)/(ENTER) character by a zero to act as string terminator,
fgets does not do this. The fgets function reads a string until the ‘ \ n’ character is found,
and then adds a zero to the end of this. This makes the string longer. For example, if year of
birth is entered as a string of four characters, it will be recorded as five characters (the ‘ \ n’
being the fifth), and will be returned into the program as a string of six characters in all,
including the ‘ \ n’ and the ‘ \0’. This means that we have to be careful about
dimensioning the strings that we shall read into, because it’s easy to fall into the trap of
assuming that the string we read back will be the same as we recorded. This behaviour of
fgets can be changed by decrementing cs in the fgets function before it is equated to zero,
but in this example, I have used the library function simplified but not returning anything
different. The other point to watch is that fgets takes three parameters, the string name, a
string length number, and the filepointer. The string-length number decides how many of
the characters of the string are read, and the function reads characters until this number is
exceeded or until a newline character is found. Unless you are very sure of your string
lengths, it’s better to provide generous values of length, so that the newline character ends
the reading action. If, of course, all of the strings were tested for length before recording,
there’s no objection to counting them back precisely. What you need to remember,
however, is that the number that you provide for string length in fgets must be the
complete string length, including the ending zero or newline character.
With these warnings in mind, we can now look at what is needed to read back the string file
that was created by the program of Figure 6.2.
#de+ine
ttde-f 1 ne
#de+ine
ttdef me
#de+ine
#de-f i ne
#det i ne
#de-f i ne
tut- -1
NULL 0
tutcll jL
true i
-false 0
N “Name - “
A “Address - "
YB “Year o-f birth
6-8
#define YJ 11 Year of joining - “
#define H "Handicap — "
ttdefine S "Subscription in pence — "
#define P “Paid, Y or N - "
char *fgets<s,n,fp)
char *s;
int n;
int *fp;
r
y.
static int c;
static char *cs;
cs=s;
whiie(—n>0 (c=getc (f p) ) i =EOF) if ((
*c5++=c)==’\nj break;
*cs=\0' ;
return s;
J
struct golf club f
char nameL20J;
char addressC401;
char birthL6U;
char joinCSJ;
char hcapL43;
char suDsCSH;
char paidC33;
>St50];
main ()
\
int x,j,count,*fp;
char c;
do
r
fp=fopen("dim","r");
J
whi1e(itestit <fp >);
f scant ( + p , “ 7.d " , Stcount > ;
fclose(tp);
tp=fopen<“c1ubdat“,‘r");
if (c oun t >5) c our* t =5;
for (j=0;j < =count-1;j++>
\
fgets(BC j1.name,20,fp >;
fqets(St j J » address,48,fp);
•f get s (S L j J . D l rth , 6, f p > ;
fgets<8Ljl.join,6,tp);
fgets(GC j J.heap,5,f p >;
f get s <G L j 3.subs,7,f p >;
fgets(GCj 1. paid,3,fp);
6-9
f close< f p) ;
pr i ntf ( " \nPress any key. . « \n ;
j=rawin ();
rawout(12);
-for (j=0; j<=count-l; j++>
printf(N);
printf("7.5\n“,GEj 3.name );
printfttt);
printf (*‘7.5\n“ ,GLj J. address) ;
printf(YB);
printf K “7.s\n'* ,GC j 3 . bi r th> ;
printf(YJ);
printf < "7.s\n'* ,Gt j 3 . join) ;
printfiH);
pr i ntf ( "7.s\n " ,GE j 3. heap > ;
printf(“Subscription:- “) ;
x—atoi (GC j J.subs);
printf i" #7.d. %d \n “ , x / 100 , x % 100 t ;
printf (F‘> ;
printf < "7.5\n“ , 8t j 3 . paid ) ;
y
j
atoi<s)
char *s;
’t
static int c,value,sign;
whiie(i55pace(*s))++s;
value=0;
sign=l;
lf < *s== - '>
-s-i-s;
sign=-1;
*.
/
else if <*s== + > ++s;
while (isdigit(c=*s++))value=10*value+c
-' 0 ;
return sign*value;
J
testlt(k)
int k;
r
if(k!=0> return k;
el ss
printf("\nNo such file.“>;
6-10
printf(“\nFind correct disc and");
printf<“\npress any key to try again.")
Reading and printing a file of structures.
FIGURE 6.3
The reading program is shown in Figure 6.3, and it starts in the usual way with the #define
steps, the fgets function, and the structure declaration. Notice that this time the structure
has been named as G[50], an array of structures. This allows us to store the structures as an
array, and demonstrate how an array of structures is handled. When the structures were
recorded, a count number was included in a separate file, dim. This is one useful way of
ensuring that the writing and the reading programs do not get out of step. In BASIC, this
number could be read and then used to dimension the array. In ‘C’ this type of thing is not
so easy, because the structure has to be dimensioned before the main program starts. If the
declaration is made before the start of the main program (that is, all of struct golf_club
but without the G[50]), then the declaration of the name (G) and the dimensioning could
be done in a function, with the structure not used in the main program. This function
would have to be called after the count number was loaded from disc. In this example,
however, we’ve kept to simpler methods, and the count number is used simply to operate
the playback loop. Once again, in a real program, the count number would be compared
with the dimensioning of the array to check that no attempt was made to overfill the array.
In this example, the line:
if (count>5) count=5
is deliberately put in to keep the amount of data down, though the dimensioning allows for
50 structures to be used. The use of count also allows a test for the correct disc being in
place before the main data is read. All of this can be replaced, and the count ignored, if the
end of file is used as a means of detecting the end of the structures on the disc, but this is not
particularly easy to do.
The example in Figure 6.3 uses the count number to set up a loop which will read in
structures. The loop runs from 0 to count-1, rather than from 1 to count, so that the first
structure (G[0]) is not wasted. If a structure name by itself, such as G[0], is used in a
function it has to be represented by its pointer, but when parts of a structure are being read,
as in G[j].name, these can be used directly. The fgets lines read in all of the parts of each
structure, after which the file is closed. The data is then displayed, in this case simply to
confirm that it has been correctly read and placed in the array. The only novelty in the
printout loop is the printing of the subscription amount. This is done using function atoi
which converts a string into an integer, providing that the string consists of digits. The
integer x is obtained from the subs string, and this is printed in pound, pence fashion by
making use of integer division and modulus operators.
6-11
Working with structure files.
The example of Figure 6.3 showed the reading part of a file being followed by a simple
printout of each record. A much more normal action would be to pick out one record, or to
sort the records into alphabetical order. Now picking out one record is fairly simple, as the
amended program of Figure 6.4 shows.
ttdefine EUF -1
#define NULL 0
#define total 2
#define true 1
#detine false 0
#define N "Name — "
#define A “Address - "
#define YB "Year of birth - “
#define YJ “Year of joining - “
#define H “Handicap - “
#define S "Subscription in pence -
#define P "Paid, Y or N - “
char *fgets(5,n,fpi
char *s;
i n t n;
int *f p;
f
l.
static int c;
static char *cs;
cs-s;
whilet—n>0 && (c=getc<fp))!=EQF>
F
t-
if (c==' \377 ')continue;
if ((*cs++=ci=='\n’) break;
~k
J
*cs= *\0';
return s;
\
y
char *getstr(s>
char *s;
r
L
static int c;
static char *cs;
cs=s;
whi 1 e < (c=getchar <) > ! =tL)P >
if (i*cs++=c)==’\n')break;
*cs-0;
return s;
6-12
struct golf club C
char nameC203;
char addressC403;
char birthC63;
char joinC63;
char hcapC43;
char subsC63;
char paidC33;
3GC503;
main ()
C
int x,j,count,*tp;
char c,51203;
/♦Length not checked*/
do
r
L
•f p=f open < "dim" , "r " ) ;
■i
J
whilet!testit (tp)>;
-f scant (tp , ‘"/.d" ,&count) ;
-f cl ose (tp) ;
t p=topen ( "cl ubdat " , "r") i
it (count>5) count=5;
tor <j=0;j<=count—1;j++)
r
V
tgets (GC j 3 . name, 20, tp),;
tgets(GC j 3.address,40,tp) ;
tgets(GC j 3.birth,6,tp> ;
tgets(GC j 3 .join,6,tp);
tgets(GC j 3.heap,5,tp);
t gets(GC j 3.subs,7, tp > ;
tgets(GCj3.paid,3,tp);
~k
J
fclose(tp >;
rawout(12);
printt("\nPlease type name required.");
getstr(s);
x=talse;
tor (j=0;j<=count—1;j++>
r
it (stremp(s,GC j 3.name))continue;
el se;
r
printt(N);
prlntt("%s\n“,GC j 3.name);
printt(A);
pr i ntt 1 "7.s\n 11 , GC j 3 . address) ;
printt(YB);
6-13
printf("%s\n",6E j 3.birth);
pnntf (YJ) ;
pri ntf ( “ 7.5\n *‘,GCj3.join);
printf(H) ;
pri ntf ( “7.5\n “,GC j 3. heap) ;
printf(“Subscription:- “);
x=atoi <GC j 3. subs) ;
printf(“#%d.XdXn" ,x/ 100,xX100) ;
printf(P);
printf < "7Cs\n " ,GCj3.paid> ;
x=true;
■v
J
break;
J
if <x==false)printf("\nName not found.")
atoi(s)
char *s;
i
static int c,value,sign;
whi1e(isspace(*s))++s;
value=0;
sign=l;
if <*s== -')
f
++s;
sign=-l;
J
else if (*s== + ) ++s;
while (isdigit(c=*s++))value=10*value+c
-' 0 ';
return sign*value;
■»
J
testit(k)
int k;
if (k!=0) return k;
el se
printf("SnNo such file.");
printf(“\nFind correct disc and");
printf("Nnpress any key to try again.")
stremp(s,t)
char *s,*t;
6-14
<*s==*t >
C
whi 1 e
£
if (!*s) return 0;
++s;
++t;
J
return *s—*t;
Reading structures into an array so that they could be sorted.
FIGURE 6.4
The principle is once more to read in the records as an array of structures, using fgets(),
which will result in each string ending with a ‘ \ n’ and a zero. When the array has been
read in, the program prompts for the name that is being searched for. This is entered, using
a function called strget(s). The reason for not using gets(s) is that gets(s) will always
replace the ‘ \ n’ character by a ‘ \ O’, and to match the string that we are entering we need
it to contain both of these characters. The function strncmp is therefore modelled after
fgets() rather than after gets(), in that it has a parameter that tells it how many characters
to copy. In addition, this program introduces the idea of comparing strings. This cannot be
done in the familiar BASIC IF A$=B$.. way. The name of a string in ‘C’ is simply a pointer
to its first character, and you can’t expect the pointers to two different strings to be equal.
Any comparison such as:
if (s==G[j].name)....
is doomed to failure. To compare strings, you need to use a string comparison function,
such as strcmp. The two strings are passed as parameters to this function, and it will
return zero (false!) if the strings are equal, true if not. In fact, if the strings are not equal, the
number that is returned is an integer equal to the difference between the string pointer
numbers, but anything which is not zero is counted as true for the purposes of a test.
The test for equality of strings is used to make the loop run faster by causing a continue in
the for loop if the strings are not equal. When a matching string is found, the else section
runs, printing the details for the selected name. Before the loop started integer x was made
equal to ‘false’ (zero), and if a string match is found, this integer x becomes true and the loop
breaks. In this way, the loop runs fast until a matching string is found, and then breaks
immediately afterwards. The integer x is used after the loop ends to print a suitable
message if no matching name has been found. It works perfectly- unless you want to get the
first name on the file! The problem with this one i&that ‘C’ reads the ASCII file starting one
character earlier than it ought to. This usually means that the first character of the file
comes in as the character that corresponds to (CTRL-Z) in ‘C’, 255, and appears as a
double-headed horizontal arrow when printed. You can’t enter this character from the
keyboard when you are asking for a name, so that the only thing to do is to remove it either
from the file or from the readback. This has been done in the program of Figure 6.4 by
modifying the fgetsQ routine. In the modified version, the character is tested for equality
6-15
to 255, and the while loop continues if this is true. The curious appearance of the test is due
to the way that a number like 255 has to be expressed. Tests of this type in ‘C’ use octal,
scale of eight code, so that 255 denary comes out as 377 octal. There is more information on
octal code in the Appendix. With this test in place, the 255 character is rejected. If the file
contains this character, of course, this would cause trouble, but an ASCII coded file would
never contain this character and if you are working with integers then you’ll use fscanf()
rather than fgets for file reading. The CP/M version of‘C’ behaves much better in this
respect. You might like to work on this program now so that the selection of a name routine
will loop until a blank name is entered (by pressing (ENTER) without typing a name).
Sorting a file.
In BASIC, there is nothing that corresponds to the structure. This makes actions such as
sorting very tedious, because each field of a record has to be represented by an array item or
as part of a string. Sorting is never easy, but in HiSoft C you do at least have the advantage
of a sort routine in the library.
Udetine EOF —i
Udetine NULL 0
#defme total 2
#define true 1
#detme false 0
ttdefine N “Name - “
#define A "Address — “
#define YB “Year of birth - "
ttdefine YJ "Year of joining - "
#define H "Handicap - "
#define S "Subscription in pence -
ttdefine P "Paid, Y or N - "
char *fgets<s,n,fp)
char *s;
int n;
int *fp;
K.
static int c;
static char *cs;
cs=s;
whiiet—n>0 && <c=getc(fp>)!=EQF>
r
v
if (c== \377 )continue;
if ((*c5++=c)=='\n') break;
J
*cs= 4 \0";
return s;
6-16
struct golfciub t
char nameL203;
char addressL403;
char birthL63;
char joinL63;
char hcapL43;
char subsC63;
char paidt-33;
J- G C D 3 , *Gp;
main ()
int y, 1 ,it,z,x,j,count,*tp;
/*Length not checked*/
do
r
fp=topen("dim", "r");
T
J
whi led test i t (tp) ) ;
tscant (f p , “7.d" ,&count) ;
tclose(tp> ;
tp=fopen("clubdat“,“r"i;
i+ (count>5) count=5;
tor (j=l;j<=count;j++)
i
tgets(GLj3. name, 20, tp) ;
tgets(GL j 3.address,40,tp >;
tgets(GC j 3.birth,6,tp);
tgets(GCj3.join,6,fp);
tgets(GC j 3.heap,5,tp);
tgets(GL j 3.subs,7,tp >;
tgets(GL j 3.paid,3,tp);
%
J
tclose(tp);
rawout(12);
printt("\nPress any key tor list");
Gp=G;
y=l;
while (y<count)
y=2*y;
do
r
y— (y--i ) /2;
lt=count-y;
for (i=i;i<=it;i++>
r
'i.
j=i;
do
6-17
z-j+y;
i -F <strcmp (Glz 3. name,GC j 3. name) <=0)
f
L
swap (Gp+z,Gp+j,sizeot (struct goltc
1 ub) ) ;
j=j-y;
J
else j=0;
J
whi 1 e (j >0 ) ;
J'
}• while (y ? = 1) ;
tor (j=l;j<=count;j++)
r
t
x=rawin();
printt(N);
printt ( "7.s\n“ ,GC j 3 . name) ;
printt(A) ;
printt ( "7.5X0" ,GC j 3. address) ;
printt (YB) ;
printt ("7.s\n" ,GCj3.birth) ;
printt(YJ) ;
pr i ntt ( "7.5\n ",GCj3.join);
printt <H);
pr i ntt ( " 7.s\n " , GC j 3 . heap) ;
printt(“Subscription:- “);
x=atoi (GC j 3.subs);
pr i ntt ( "#7Cd. V.d \n “ , x / 100, x V. 100) ;
printt(P);
pr i ntt ( "XsXn ",GCj3.paid>;
printt("XnPress any key...");
atoi(s)
char *s;
'v
static int c,value,sign;
whi1e(isspace(*s))++s;
value=0;
sign=l;
it (*s=='-')
r
L
++s;
sign=-l;
else it <*s=='+ ) ++s;
6-18
whiie (isdigit(c=*5++))value=10*value+c
- ' 0 ' ;
return sign*value;
J
testit(k>
1 n t k;
r
i-f(k?=0> return k;
el se
printf { “ \nNo such tile.”);
printt(“\nFind correct disc and");
printt(“\npress any key to try again.“)
strcmp(s,t >
char *s,*t;
r
v
while (*s==*t)
f
it <!*s> return 0;
++s;
++t;
Ti
J
return *s-*t;
A structure array program which incorporates a sort routine.
FIGURE 6.5
The illustration of Figure 6.5 shows a Shell-Metzner type of sort routine used to sort a list
of records in alphabetical order of names as typed in the file. Now this is not the Shell-
Metzner sort which is included in the library, because that one is a rather complicated
general-purpose one. Nor is it the sort that is mentioned in the manual, from Kernighan &
Ritchie’s book, because it won’t compile into HiSoft C as it stands. The problem is that
there are three loops, and this causes a ‘Too many operators’ message when you try it. The
one in the library only just fits in. Because of all that, and for the sake of variety also, I have
put in another Shell type of sort, adapted from a version which I wrote in Pascal.
The program reads the count number, and then reads the structures into an array. This
time, simply because it’s more convenient for the sort routine, the array numbers start with
1 rather than with zero, but that’s the only change up to the point where the message ‘Press
any key for list’ is printed. After that, things get more complicated. In the structure
declaration, the usual G[5] has been supplemented by *Gp, making Gp a pointer to a
structure. Simply declaring that Gp is a pointer, however, doesn’t make it point to
6-19
anything, and the statement Gp=G is needed to make Gp a pointer to the start of structure
G. This is a very important step, and omitting it is one of the most common errors in the use
of pointers. If your pointer hasn’t been set to point at something, then trouble, in the shape
of a major program crash, can’t be far behind. Why do we need the pointer anyhow? The
answer is that in the sort routine we shall want to change the pointers to different members
of the array. Changing pointers involves exchanging only two numbers, rather than the set
of strings (or whatever else is used) in a structure. For this reason, it’s fast and simple.
I won’t go into details of how the Shell-Metzner sort works, because it’s a standard routine
that you can find described at length in many other books. The important features of the
sort (starting at the statement y=y+l) are the test and exchange steps. The test uses
strcmp as you would expect, with G[].name being used as a basis of comparison. The
exchange step uses the built-in function swap, and the parameters that are supplied make
use of the string pointers, along with the sizeof statement. The sizeof statement, as its
name suggests, will provide an integer, the number of bytes of memory allocated to a
variable. In this case, it’s the structure golf_club whose size we want to pass on. We can
count it up for ourselves by adding the bytes allocated to each part of golf_club, and it
comes to 85, but by using sizeof the counting is automatic, so that if you alter the structure
dimensioning you don’t have to alter the swap routine. The swap routine exchanges
pointers, if need be, and when the sort is completed, the structures will be in order ofnames.
The important point is that, because of the Gp=G step, you can print out this new order
using G[j], you don’t have to use pointer Gp unless you want to. This is the value of
altering pointers in this way, because the pointers can be altered in a subroutine and the
alteration will affect the result of a printout in the main routine. In this example, the whole
sort routine is, unusually, in the main program, simply to avoid the problems of passing
parameters until you have seen an example of the straightforward version. The remaining
parts of the program are straightforward, and a ‘Press any key’ step has been put into the
loop which prints the details so that you don’t lose data because of scrolling.
The next problem is how you would alter this program so as to sort in order of ascending
handicap numbers. I have not used handicap numbers in the strict golfing sense here, so
please don’t write to say that the range should be from about +4 to -36! It’s not so easy as
you might think, because simply using G[].hcap does not do what you might think. This
change will work if the handicaps are single figures (OK for an Open championship,
perhaps), but not for double figures. The reason is that a number like 20 is taken as being
less than the number 6. This is because the G[].hcap field is a string, not an integer, and
the comparison goes one character at a time. When a ‘2’ is found as the first character, this is
taken as being less than the ‘6’, and so the second character of the ‘20’ is never considered.
How do we get round that one?
The answer is a simple alteration to the comparison line, using atoi in place of strcmp.
The line now becomes:
if (atoi(G[z].hcap)<=atoi(G[j].hcap))
In this line, if the handicap of member z is less than or equal to the handicap of member j,
then the swap will be carried out as before. The important point, once again, is that the
effect of swapping an entire record can be carried out by swapping pointers.
6-20
Now suppose that we wanted to sort the list of names in some other way. In Figure 6.5, we
chose to sort in alphabetical order name, and we have seen also how to sort by ascending
handicap number. Sorting by descending handicap is trivial, all you need to do is to
reverse the <= sign in the comparison line into a >= sign. The important change is to be
able to sort ‘by another field’. In plain language, this means sorting by surname order, or
forename order, by age, or by any other feature. This is easy enough if you want the sort to
be permanent. If, for example, you always wanted the program of Figure 6.5 to sort the
records in order of handicap, then all you have to do is to carry out the changes that we have
looked at. It’s not quite so straightforward when sorting might be wanted by any of a
number of fields, selected when the program runs. The obvious way to do this is to
introduce a menu stage in the main program, and select each time the comparison that is
needed when the program of Figure 6.5 runs.
#define EOF -1
♦♦define NULL 0
#detine total 2
Ude-fine true 1
(♦define false 0
♦♦define N "Name — "
♦♦define A "Address - "
♦♦define YB "Year of birth — “
#define YJ "Year of joining — "
♦♦define H “Handicap - "
♦♦define S "Subscription in pence - "
♦♦define F‘ "Paid, Y or N -• “
char *fgets(s,n,fp)
char *s;
int n;
int *fp;
't
static int c;
static char *cs;
cs=s;
whilet—n>0 && (c=getc(fp>>!=EOF)
L
if <c== '\377')cont inue;
if ((*c5++=c)=='\n'i break;
"k
J
*cs= V0 ;
return s;
■k
j
struct golf_club i
char nameL203;
char addressE401;
char birthCEl;
char joinCSJ;
char hcapE4J;
char subsLEl;
6-21
char paidC33;
J' G C 5 3 , *Gp;
main ()
t
int s,y, i ,xt,z,x,j,count,*tp;
char c;
/♦Length not checked*/
do
t
fp=+open("dim","r");
•»
J
whi1e(!testit <t p >);
t scant (f p , "%d " , Stcount > ;
-f cl ose < tp > ;
+p=topen("clabdat","r");
it (count>5) count=5;
tor (j=l;j<=count;j++)
f
V.
tgets(GCjl.name,20,tp);
tgets(GL j 3.address,40,tp);
tgets(Gtj3.birth,6,tp);
tgets(GtjJ.join,6,tp);
tgets(GL j 3.heap,5,tp);
tgets(GL j 3.subs,7,tp);
tgets(GL j 3.paid,3,tp);
J
telose(tp >;
dot
dot
rawout(12);
prlntt("\nPlease select sort method.");
printt(“\n 1. Name.");
printt("\n 2. Age.“);
printtC \n 3. Length ot membership.");
printt("\n 4. Handicap.");
c=getchar() ;
c—=48;
3while(c<0 !! c>4);
Gp=6;
y=l;
while (ytcount)
y=2*y;
do
r
t
y=(y—1)/2;
it=count-y;
tor (i=1;i<=it;l++)
6-22
j=i;
do
r
'i
z=j+y;
s=tal se;
switch (c)£
case i:it (strcmp(GCz].name,GCj3.name
)<=0> s=true;break;
case 2: i t (atoi(GCz3.birth><=atoi(GCj
3.birth))s=true;break;
case 3:it (atoi (GCz3.join><=atoi (GCj3
.join))5=true;break;
case 4:it (atoi(GCz3.heap><=atoi(GCj3
.heap))s=true;break;
3
it (s) t
swap (6p+z,Gp+j,sizeot (struct golfc
lub) ) ;
j=j-y;
1
j
else j=B;
J
whi 1 e (j >0);
J
3 whlie (y! = 1) ;
print-f ("NnPress any key. . . \n“) ;
tor (j=l;j<=count;j++)
r
v
=rawi n () ;
printt(N);
printf ( "7.s\n " , GC j 3 . name) ;
printt(A);
pri ntt ( "7.5\n " ,GC j 3 . address) ;
printt(YB>;
printt ( “7.s\n ‘‘,GCj3.birth) ;
printt (YJ);
pr i ntt ( “7,s\n " ,GLj3.join);
printt(H);
printt ( “7.s\n " , GC j 3 . heap) ;
printt(“Subscription:- ");
x=atoi(GCj3.subs);
pr i ntt ( "#7.d. 7.d\n “ , ;< / IBS, x7.100) ;
printt(P>;
pr intt ( "7.s\n " , GC j 3 . pai d) ;
printt(“\nPress any key-..“);
6-23
printt<"\n@ Key terminates display");
>whi1e (x=rawin O !=©');
J
atoi(s)
char *s;
t
static int c,value,sign;
whi 1 e < isspace(*s) )++s;
value=0;
sign=l;
it <*s=='-)
t
++s;
sign=-l;
y
else it <*s=='+) ++s;
while (isdigit(c=*s++))value=10*value+c
-' 0 ' ;
return sign*value;
y
test!t(k >
int k;
r
K.
it<k!=0) return k;
el se
r
v
printt("\nNo such tile.");
printt(“\nFind correct disc and");
printf("\npress any key to try again.")
strcmp(s,t)
char *s,*t;
C
while <*s==*t)
it <!*s) return 0;
++s;
++t;
n.
return *s-*t;
Amending the program so as to sort by any field.
FIGURE 6.6
Figure 6.6 shows the amendments that can be made to allow a choice of four possible sort
fields, in this case, surname, year of birth, joining year or handicap. The sort and display
part of the program has been arranged so that it will loop until the @ key is pressed. This
allows you to enter data from the disc file and then test the action of the switch function.
Now look at the program of Figure 6.6 in detail. Two more variables have been declared.
One is c which will be used to accept a letter choice when you are asked to decide which field
to use for sorting. The other is s, which is an integer variable that will be used to decide
whether to swop two records or not.
The selection action starts immediately after the data has been read in from the disc. The
choices are listed, and you are asked to select by number. This is done within a do loop, so
that when the number that you use is tested the menu will repeat until a correct choice is
made. This, incidentally, causes the menu to perform several flashes at times when several
characters are in the keyboard buffer. When an acceptable number is pressed, the function
returns to the main program with the value of a number assigned to c. This is then used in
the sorting function. In the record exchanging part of this routine, variable s is set to false
and the lines that follow switch carry out the selection of field. In these lines, then, the
letter which has been coded as c is used to select the correct comparing action, testing the
correct field of each record. As a result of that test, s will be either TRUE or FALSE. If s is
TRUE, then the test in the exchange portion of the sort routine will cause the records to be
exchanged. Note that you don’t have to type:
if (s==true)....
or
if (s!=0)....
only if (s)..., because this is the correct syntax in ‘C’ as it is in many versions ofB ASIC. The
rest of the program then proceeds in the usual way.
This method of choosing which field to sort by can be used when the number of records is
comparatively small, but causes problems when a large number of records are used. The
problem is one of sorting time. The switch selection line has been placed in a part of the
sorting loop which is repeated very many times during a sort, and as a result, has a large
effect on the total time that is taken. Because ‘C’ is a compiled language, the effect is not so
serious as it would be in BASIC, but it can make the sort action irritatingly slow. If you need
to exercise a choice of sort field for a list of records which consists of a large number of
items, all held in the memory, then a faster option is to have as many sort functions as you
want sort fields, and to select the complete function. You might, for example, have
functions sortname, sorthcap and sortage which were selected by the value of c. Each of
these functions would be identical apart from the test line in each one. This method
requires more code, but runs much faster. The reason is that the choice is not having to be
enforced each time two items are being compared. The choice is made once and used to
select a function which can then run unencumbered. As usual, when you design a program
you can design it to run fast, or you can design it to be comparatively short, but you can’t
normally have both! The alternative is to write a sort routine to which the parameters can
be passed, but for a structure this is extremely difficult because you have to be able to pass
the field names such as G[j].name , not just the array name.
6-25
Record nests and choices,
So far, each record that we have illustrated has consisted of items that are simple variables.
We can, however, use records which consist partly or completely of other records!
Structures which are a part of another structure are called ‘nested’ structures, and typically
they are used to hold details of an entry. We might, for example, have an entry called birth
which would require the details of day, month and year of birth. This could be provided by
making birth a structure in itself, with day, month and year items of that structure. Figure
6.7 shows how this provision for nested structures can be used.
ttdetine max 2
char *getstr(str,count>
char *str;
int count;
r
v
static int c;
static char *cs;
cs=str;
whilet—count>0 && ((c=getchar<)>!='\n‘
) )
*C5++=c;
*cs=0;
return str;
1
J
struct name(
char sur E20 3;
char frnL203;
>;
struct dob C
xnt day;
int month;
int year;
>;
struct personi
struct name memnam;
struct dob birth;
char phone[163;
J member Cmax+13;
main ()
•C
char replyCISJ;
int j ;
-For ( j = i ; j<=max ; j++)
t
printf("\nSurname - " ) ;
getstr(member L j J.memnam.sur,20);
print-f ( "\nForenarae - “ ) ;
6-26
getstr(member tj3.memnam.frn,20);
printf("XnDay of birth i—31 —"
getstr(reply,4) ;
member!j 3.birth.day=atoi(repiy);
printf ( " XnMonth of birth 1-12 — " );
getstr(replv,4) ;
member Cj3.'birth. month=atoi (reply) ;
printf(“\nYear of birth - “);
getstr(reply,6) ;
memberCj3.birth.year=atoi(reply);
printf(“\nPhone number - “);
getstr(memberL j 3.phone,17) ;
J
rawout(12) ;
printf (“ \n7.20s“ , “LIST" ) ;
printf("Xn");
for (j=l;j<=max;j++)
C
pr i ntf ( " Xn/is , %s",memberCj3.memnam.sur
,member C j 3.memnam.frn);
printf("XnBorn - ");
printf ( “7.d-y.d “ , member Cj3.birth, day , memb
er Cj3.birth.month);
printf ( V.d " , member C j 3 . bi r th. year ) ;
printf (“ \n7.s— '/.s\n ", "Phone No. ", member tj
3. phone);
atoi(s)
char *s;
f
static int c,value,sign;
whi1e(isspace(*s))++s;
value=0;
sign=l;
if (*s==‘-'>{++s;5ign=-i;}
else if (*s=='+') ++s;
whi1e(isdigit(c=*s++))vaiue=10*value+c-'
0'S
return sign*value;
Using nested structures. This will be illustrated further in Chapter 9.
FIGURE 6.7
6-27
The main structure now is of type person, but it now contains the sub-structures name
and dob. The structure variable memnam is of type name, and birth is of type dob, both
of which must be defined as structures before the main structure can be defined. Structure
name is defined as consisting of sur and frn, both arrays of char. Remember that you
can’t use for for forename, because this is a reserved word. The structure dob consists of
day, month and year, all integers. These ranges would, in a working program, be checked
each time an item was entered.
Though the structures are nested, we don’t necessarily need nested loops to read items into
the structures, or out of them. For the sake of simplicity, all stages except entry and printing
have been omitted from this program. The entry function starts early in the main program,
and the important feature here is that the reading line uses the full title for each field and
subfield. For the first member, 1 in this case, we need the surname. This has to be specified
as:
member[j].memnam.sur
using the main structure title, the substructure title (memnam) and the field title of sur.
Each name and number is entered in this way, using a string entry function which is
modelled after fgets() so as to restrict the number of characters entered. Remember that
the number in this function must be one more than the number of characters, and that a
string will always contain a zero character. This is why the day of the month, for example,
which has two digits at most, needs a count number of 4 in getstr. Following these entries,
the phone number is obtained. Note that this is put into string form. A telephone number is
most unlikely to be expressable as an integer, and because it may contain dashes (as in
0999-1123-212), it cannot be expressed as a real (float) number either when this becomes
possible. The way in which nested structures are printed out is illustrated in the lines
following the printing of the title LIST. The loop construction is just as it was before, and
the items are specified in the same way. Note how the date of birth items have been fielded
so as to make the date look better on the screen. This example shows the most awkward
type of sub-structure identification, in which there are several subfields of one main field.
6-28
Chapter 7
More about pointers.
We have made some use of pointers in programs and program functions, but the subject so
far has really only been introduced. If you look through the routines in the library, you will
find that practically all of the standard routines make considerable use of pointers, rather
more than we have done so far. The intelligent use of pointers can make a lot of apparently
difficult program actions become relatively simple to achieve. A good reason for leaving
detailed discussion of pointers until later in this book, however, is that the careless use of
pointers can make a program unworkable. In many respects, the use of pointers in ‘C’ is
rather like the use of GOTO in BASIC - it can make a lot possible, but that can include a lot
that you don’t want.
Before we start on extended pointer exploration, then, recall for a few moments what a
pointer is. A pointer is a number which locates a piece of data. A pointer can be defined as a
pointer to an int, char or any other data type, simple or complex. If the data type is a simple
one, the pointer is the number which gives the location of the first byte of that data. If the
data type is complex, like an array or a structure, then the pointer gives the address of the
start of the array or structure. If we want to make use of a pointer, we must declare its name
and also assign it. We can carry out actions on pointers that include incrementing and
decrementing, addition and subtraction of integers, comparison of pointers, and the
subtraction of one pointer from another (only for pointers of the same type). The valuable
feature of pointer arithmetic is that ‘C’ makes automatic allowance for the size of data. If
you have an array of characters, for example, then you can define and assign pc as a pointer
to the first character. Incrementing this pointer, either by using ++ or by adding 1, will get
a pointer to the next character. Since each character takes up just one byte of memory, this
isn’t exactly surprising. If you have an array of integers, however, in which each integer
uses two bytes of memory, then changing the pointer by using ++ or by adding 1 will still
get the next integer, even though the pointer has to change by two bytes this time. This is
extremely valuable, because it means that you don’t continually have to be worrying about
the numbers that you add to pointers. You can, of course, add numbers greater than 1 if you
want to get hold of other parts of an array.
7-1
main ()
static char namL ] = ‘*Sincl air 11 ;
static int datat3=£1956,1966,19833;
char *pc;
int *pn;
pc=nam;
pn=data;
write(pc,8);
dates(pn,3) ;
■fc
J
write(p,n >
char *p;
int n;
r
■_
int j;
rawDut(12);
rawout (12!) ;
for (j=l;j<=n;j++)
putchar<*p++);
Tl
J
dates <p,n)
int *p,n;
T
int j;
tor (j=l;j<=n;j++>
pr i ntt ( “ \n>.d 11 , *p + +) ;
Passing pointers to functions to be used in the functions.
FIGURE 7.1
The use of pointers in this way, along with passing pointer to functions, is illustrated in
Figure 7.1. In this example, two arrays have been declared and initialised. One is an array of
characters, the other is an array ofintegers. The assignments pc=nam and pn=data will
make the pointers point to the start of each array. Note that this type of assignment is legal
because the name of an array is also the value of its pointer. The important difference
between the pointer that we assign and the name is that the name is a fixed pointer. In
other words we can assign pc=nam, because pc is a pointer variable, but we cannot assign
nam=pc because nam is a fixed amount, the pointer for the start of an array, which
cannot be altered except by assigning another array. This important difference is not
always well emphasised in books. We could also, incidentally, assign directly to the
pointers, not using the array names at all, and this is something that we’ll do later. Once the
pointers have been assigned, we can use them in function calls. Two function calls are
shown, one to write(p,n) which will print a string of n characters pointed to by p, and
dates(p,n) which will print n dates, one on each line, pointed to by p.
7-2
The real meat of pointer use is now contained in the function definitions, and we’ll look at
write first. The header contains the parameters p,n and these will have to be declared
before the first curly bracket of the function, because these are the variables that are passed
to the function. Since p points to a character, it is declared as such, and n is an integer.
Remember that these names are completely local to the function, we can change them
without altering the quantities that are stored as pc, pn, n in the main program. Following
the first curly bracket, the integer j is declared for the loop, and the loop uses putchar() to
print a character on the screen. Function putchar is built-in to HiSoft C, and so it doesn’t
need to be typed in. The character that putchar uses is *p, the character that pointer p
points to. At the start of the loop, p takes the same value as pc, because this was the value
passed to it. In the putchar() statement, however, we use *p++ so that the value of p is
incremented by one character position after the character has been printed. This will
ensure that the next character is fetched when the loop goes around again. The dates
function behaves in an almost identical way, but printf has been used to ensure that the
date is printed in the form of an integer number. Once again, using *p++ ensures the
correct next number, though this time the memory is being incremented by two units
instead of just one. We could make the action of the write routine more elegant and more
useful by making it write all characters to the 4 \ 0’ character.
write(p)
char *p;
rawout<12);
rawout(lid) ;
whlie t *pi= \0')
putchar t*p + +);
Simplifying the writeO functions.
FIGURE 7.2
This modification is illustrated in Figure 7.2. Using this function now requires only the
pointer to be passed, not the number of characters.
So far, so good. If you want to pass a pointer to a single integer or character, you must use
the pointer-finding symbol &, which has been illustrated previously. This is particularly
important when functions such as scanf are used. Generally, however, the main use of
pointers is in connection with arrays because this is one of the ways that arrays can be
manipulated as a whole. As an example, take a look at the program in Figure 7.3.
7-3
main (>
static char namL]="Sinclair";
static char town[]="Watford“;
char *pc,*pd;
pc=nam;
pd=town;
printt ( “ \nXd ,”/.d“ ,pc ,pd) ;
printf ( " \nXs ’/.s",“Name is“,pc) ;
printt (“\n7.s y.s‘‘, "Town is" ,pd);
exchange (&pc , &pd > ;
pr i ntf ( “ \n'/.d , V.ti “ , pc , pd) ;
printt ( “\n'/.s %s“,“Name is",pc);
printt ( "\n7.s '/.s",‘ , Town is" ,pd>;
exchange(x,y)
int *x,*y;
w
int tmp;
tmp = *x;
*x=*y;
*y=tmp;
Swapping string pointers so as to interchange the strings.
FIGURE 7.3
This contains a function exchange which will swap two strings of different lengths, by
swapping their pointers. Now it’s important to realise that only the pointers are swapped,
and the strings remain assigned to their original names. If we print out the string names
before and after swapping, there will be no change. If we print out using the pointers,
however, the swap will be obvious. The moral, then, is to work with pointers at all times if
you are going to make such changes. The routine of Figure 7.3 declares and assigns two
names, and the pointers are declared and then assigned. The printf lines then show what
the pointer addresses are. If it still unnerves you to see these as negative numbers, use “%u”
in place of “%d” in the printf lines for printing the pointers. The first printing shows the
pointer numbers and the names in the correct order. Following the exchange function,
however, the pointer numbers are swapped, and what they point to is also exchanged. This
appears only if we printf the pointers pc, pd, not the names nam and town which do not
change. Note that the printf line uses pc,pd and not *pc,*pd. Once again, this is because
the name of an array is the pointer to its starting address. In this context, quantities such as
*pc,*pd are meaningless. The pointer exchange is carried out using pointers to the
pointers. The pointers to the strings are simply two numbers which are stored in the
memory. To exchange them in a function we need to find where these pointer numbers are,
and we can do this by finding their own pointers. This is done by calling function
exchange with parameters &pc, &pd, which are the pointers to pc, pd respectively.
7-4
These pointers are passed to the function as numbers x and y, and defined as pointers to
integers, which they are. The integers that they are pointing to are the pointers pc,pd. We
then use these pointers to exchange the values of pc and pd. We cannot simply exchange pc
and pd in a function, because the function works with local values only. At the end of the
function, any quantities that are passed to the function are restored to normal. If we work
with pointers, however, we can make permanent alterations in anything that these pointers
point to. In this case, the quantities that we are working with are temporary values x and y.
These are manipulated so as to exchange pc and pd, pointers which have not been directly
passed to the function. Using pointers in this indirect way is the only method by which a
function can make changes in a number of parameters. The routine carries out the swap,
and when the main program takes over again you can see that the pointers have been
swapped. The important feature here is that a pointer is a two-byte number. You can swap
pointers like this around as much as you like, and the action is quick and easy. It’s certainly
not so easy, and definitely not so fast to try to swop the actual contents of strings around.
Y ou wouldn’t be advised to try it on strings of unequal defined lengths, either, but when you
work with pointers all things are possible. If, incidentally, you want to find where the
pointers to the pointers are stored, add a line:
printf(“ \ n%u,%u”,x,y);
just following the int tmp declaration in the function.
Arrays of pointers.
An array of pointers is a method of locating data which is often more useful than other types
of arrays. It would be rather pointless (sorry!) to use an array of pointers to integers,
because it’s simpler to use an array of integers, and it would take less space. Arrays of
pointers come into their own when they are used to refer to arrays of arrays. An array of
strings, for example, consists of an array of arrays of characters. A useful alternative to a
string array formed in the way that we have used previously, then, is an array of pointers to
strings. As we have seen, this allows for actions such as exchanging to be carried out. To
form and make use of such an array of pointers we have to know what syntax has to be used
to refer to pointer arrays. Figure 7.4 shows a simple start to the idea.
main(>
A.
static char siC3="Xerography“;
static char s2t]=“Aiteration“;
static char s3C3=“Middle";
char *ptrt3j;
int j;
ptr Lt53=sl;
ptr L 1J=s2;
ptr L 2J=s3;
tor (j =0;j <=2;j ++ >
pr l ntf ( “ \n’/.s“ , p tr L j 3 ) ;
J
Using a pointer array as a string array.
FIGURE 7.4
7-5
Three strings have been declared and initialised. They have been declared as static
because otherwise initialisation cannot be carried out on the same step. An array of pointers
is then declared, using ptr[3], and the strings are assigned to the pointers. In a ‘real-life’
program, it’s likely that you would use a scanf function to get the strings from the
keyboard, so that the assignment would be direct to the pointer array elements. The
program then prints the strings in order of assignment.
Now this is all very tame stuff, but you can see that the use of arrays of pointers can lead to a
lot of interesting actions. It’s as easy to select one string in this way as in a conventional
string array, and it’s much easier to swap pointers round so that the string arrangement is
different.
main ()
r
X.
static char siE] = "Xerography";
static char s2E3=“Aiteration“;
static char s-3E J = "Midal e" ;
char *ptrE33;
int p;
int j;
ptr E0 3=si;
ptr E1J=s2;
ptr E2D=s3;
+ or < j =0;j <=2;j ++)
printf ( 11 Xn'/.s 1 ' ,ptr E j ] ) ;
alter <&ptrE03,&ptrEll,&ptr E2D);
•f or < j =0; j < —2 ; j ++ )
pr i nt+ i “ Xn'/.s" , ptr E j 1) ;
*»
J
alter(x,y,z)
int *x, *y , *z ;
i
int tmp;
tmp=*x;
*x=*y;
*y=tmp;
tmp=*y;
*y=*z;
*z=tmp;
Using a pointer array to swop string positions.
FIGURE 7.5
7-6
Figure 7.5 shows this done, using a simple swap routine rather than the complications of a
full-scale sort for so few items. Note that, contrary to what you might expect, you have to
use the address sign, &, in front of the pointer array values in order to pass the values
correctly to the function. If you omit the & signs, you will find that the first two letters are
exchanged, but no others. This is not a standard action of‘C’, and it’s not easy to see why
two characters are swopped. One, or all, I could understand, but two? Another point to note
is that the use of a function will successfully result in obtaining pointers to the pointers, but
this is not so simple if you want to carry it out in the main part of the program. The reason is
that quantities such as ptr[0] are defined as string pointers, and you can’t obtain pointers
to them in a straightforward way. The only simple assignment you are allowed to make is of
another pointer to a string. You can get around this restriction by using a statement cast
which is very poorly illustrated in most books on ‘C’. The use of cast is to make a quantity
become of a specified type, and the syntax is cast(type)quantity. This doesn’t illustrate
how cast is used, however, quite so dramtically as Figure 7.6.
main ()
X.
static
char
siC J = "Xerography“;
statlc
char
s2C 3 = "A1teration“;
static
char
s3L 3 = "Middie“;
scatlc
char
*ptr C3J;
static
int j;
l nt p
, *s ;
ptr L0J =
si;
ptr Ci } =s2;
ptrC2d=s3;
s=&(cast (int) ptrC03);
-for < j =0; j< =2; j + +)
printt < “ \n7.u, 7.u" , s , *s) ;
pri nt+ ( “ \n7.s“ , *s++i ;
Using cast to convert a pointer into unsigned form.
FIGURE 7.6
In this example, the strings have been printed in quite a different way that illustrates the
usefulness of pointers to pointers. The line:
s=&(cast (int) ptr[0]);
will have the effect of making the quantity ptr[0] temporarily into an integer, and then
taking the address of this integer and assigning it to integer pointer s. It is possible to do this
in two steps, such as:
p=cast(int)ptr[0];s=&p;
7-7
but when you do this the addresses will be displaced, because of the extra integer, p. If you
use this method, you will find that the output is decidedly peculiar, with the first string
being printed twice! Using the correct cast expression allows s to carry the address of
Ptr[0], and the printing loop can now make use of *s++ both to print the value and to
increment to the next string. This is simple because the string pointers are held at
consecutive addresses.
Another aspect of the use of pointers with string lists is how easily an item can be located.
This makes for very efficient and short routines for such actions as finding the day of the
week from a number.
char *getnarae(c)
i n t c;
£
static char *dayC3=£
"no such day“Monday"Tuesday",“Wedne
sday","Thursday",“Friday","Saturday",
"Sunday"3;
return <(c<ii Ic>7)?dayC03:dayLcJ);
1
J
main <)
£
char c;
rawout i 12);
printt("\nDay number, piease\n">;
c=(getchar() — 48 ) ;
print-f < "\n7.s" ,getname (c> ) ;
Using a string pointer array, initialised at the same time as being declared.
FIGURE 7.7
An illustration of this use is shown in Figure 7.7, which also shows the initialisation of a set
of strings in a function. The function is of type char and it will return a pointer because this
is what the name of one item will be, one of the array of pointers. The header will put in an
integer which will consist of a number in the range 0 to 9. A number such as 10 will count as
1, because only the first character will be accepted by the main program call to getchar.
This number is declared and treated as an integer in the function, and the pointer array
*day is declared and (since it is static) assigned. The assignment of the zero position is
made to a message. After this, the last fine of the function returns the selected string. The
variable c is used as a selector in the line:
return((c<l I I c>7)?day[0]:day[c]);
so that if c is less than 1 or more than 7, the string day[0] will be returned, giving the
message ‘no such day’. For numbers between 1 and 7 inclusive, the correct day of the week
is returned counting Monday as day 1.
7-8
Pointers to functions
One of the restrictions about the use of functions is that you can’t use a function name as a
parameter to a function. It would be useful, for example, if a function could specify in its
header which of a number of alternative functions was to be used inside it. This action is,
however, possible provided that the name which is passed is treated as a pointer to the
function that is wanted. Once again, the use of a pointer makes a very useful and curious
action possible.
main ()
static char testL3=“TeSt";
int down(),up();
int j;
printt ( “\n7.s" , test > ;
tor (j=0;j<=3;j++)
r
v
it (testCjD<91> testCjd=redo
P> ;
else it (testCj1>V6)testCj3 =
j 3,down);
1
J
printt ( “\n7.s" , test) ;
>
redo(c,change)
char c;
int (^change)();
F
t.
c=i*change) (c);
return (c);
(t est C j 3 , u
redo(test L
up (s)
char s;
s+=32;
return(s);
l
J
down(s>
char s;
(.
s =: -j2 \
return is);
Passing a pointer to a function to allow a choice of functions.
FIGURE 7.8
7-9
Figure 7.8 illustrates this way of allowing functions to pass other functions as parameters.
The example is a very simple one, which means that what it does could very easily be done
by simpler methods. The point, however, is that its only by looking at fairly simple
examples that you can disentangle the important features from their surroundings. In this
example, the program is provided with a word which is typed partly in upper-case and
partly in lower-case, and the aim is to reverse the case of each letter.
The main program is simple enough, but the declarations have to be watched. The
functions that are going to be passed as parameters have to be declared at the start of this
main program, and they are called down and up. Each of them will return an integer, so
that they can be placed at the end of the listing without any need for references forward.
Following the declarations, the program starts a loop in which each character of the word is
selected and passed as a parameter to a function called redo. This function does not need to
be declared in the main program, and it will be designed to return the character rather than
altering a pointer. Because it returns a character, its form is:
character=function(parameters)
and in the example, the two different calls to redo are placed in two test lines. If the
character that is being dealt with has an ASCII code of less than 91, then it is an upper-case
character, and function up must be called within the redo function. This is done by using
the call:
test[j]=redo(test[j],up);
in which we want to alter test[j] by equating it to the character returned by redo. We also
want redo to make use of function up, and this function name is put into the parameter list
for redo. Note that we use up, and not &up. This is because ‘C’ takes the name of a
function, like the name of an array, as a pointer to where the function starts. The alternative
call is made if test[j] is greater than 91, when the call to redo makes use of the function
down.
The next step is to look at function redo to see how the alternative functions up and down
are used. The header for redo uses parameters (c,change) in which c is a character code
and change represents the pointer to a function passed to redo. This function represented
by change has to be declared before opening the curly bracket on redo, and it’s declared as:
int (*change)();
- a function which returns an integer and whose (temporary) name is pointed to by
change. You must use the name as a pointer here, with the asterisk, and the brackets
surrounding the function name, and a separate set of brackets as you have with any
function. The redo action then consists only of calling the function that has been passed to
redo, and returning the correct character. Once again, however, the syntax is important.
The temporary name must be used as a pointer and enclosed in brackets, with its parameter
c in separate brackets following (*change). Since this function needs to return a
character, we use:
c=(*change)(c);
return(c);
7-10
to ensure the return of a suitably changed character. The effect of all this will be to pass the
pointer to one of the functions up or down, and execute the action of that function from
within redo. It’s not exactly the kind of thing that you can do in BASIC, and it’s one of the
many features that makes ‘C’ such a very powerful language for programming. The
functions up and down are written conventionally, but the lines return(s) are, in fact,
redundant. They have been put in as a reminder that each function returns something, but
they can be deleted because the return(c) inside redo carries out the returning action.
Passing function names in this way is particularly useful when you want to use a function
on different types of data. One very common type of action is sorting lists of different items.
This can call for the use of different comparison functions (one for numbers, one for
strings) and diffent exchange functions (once again, differing for numbers and strings) but
with the same basic action, such as the Shell sort.
/* Sorting function - a Shell sort */
void qsorttlist, numiterns, size, cmpfu
nc)
char *list;
int numiterns, size;
int (»cmp_tunc)<);
static unsigned gap, bytegap, i;
static char *p;
for (gap = numiterns >> 1; gap > 0;
gap >>= 1)
bytegap = gap * size;
for <i = gap; i < numiterns; ++i
)
for (p = list + i * size - byte_
gap; p >= list; p -= bytegap)
r
«.
if ((*cmp_func)(p, p + byte_
gap) < = 0) break;
swap(p, p + bytegap, size);
The Shell sort from the HiSoft library, courtesy of HiSoft.
FIGURE 7.9
7-11
You can obtain this by using g„stdio.lib, and you can then separate out routines and
record them for use as and when you want. In this particular example, four variables are
passed, one of which is a function cmp_func. This will be the function that compares
items in an array. The start of the array is pointed to by list, and the size of the items must
be constant. The number of items and size is passed as integers, and the comparison
method is a function.
The action of calling the sort is better illustrated by an example, in Figure 7.10.
main <>
X.
int numcmp() ,strcmp l > ;
static int t J= 13,17,4,21,163 ;
static char namC5JE103=t“zebra","delta",
“whisky",“juliet",“echo"3;
int j;
for (j=0;j<=4;j++>
pr i ntf ( " \nV.d-7.s" , x t j li, namL j 3 ) ;
printf<"\n">;
qsort (;< ,5,2,numcmp) ;
qsort(nam,5,10,strcmp );
f or (j =0;j < =4;j + +)
printf ( " \n7.d-7.5" ,L j 1,namL j 1) ;
"»
J
int qsort(list, numiterns, size, cmp fun
c)
char *list;
int numiterns, size;
int i*cmp_func)(>;
static unsigned gap, bytegap, i;
static char *p;
for (gap = numiterns >> 1; gap > 0 ;
gap >— 1 )
\
bytegap = gap * size;
>
for (i = gap; i < num_iterns;
++i
for ip = list +- l * size -
byte_
gap;
p >= list; p -= byte_gap)
if ((*cmp_func><p, p +
byte_
gap)
<= 0) break;
swap ip, p + bytegap, size);
7-12
int strcmp(s, t)
char *s, *t;
r
*c
while (*s == )
F
l.
i-f (i *s) return 0;
++s; ++t;
■k
y
return *5 - *t;
\
J
int numcmp(x,y)
int *x, *y ;
{
it (*x>*y)return (1);
else it (*x<*y) return ( —1);
else return(0);
Making use of the library function qsort.
FIGURE 7.10
In this example, two arrays are declared and initialised. One is an array of integers, the
other an array of strings which is declared as a two-dimensional array and filled with
words. Note the order of the subscript numbers, which is [number of words] [maxi¬
mum size]. These arrays are printed side by side to show the order, and then two calls are
made to the function qsort. This has been loaded in from the library with only one change.
The change is to make the type of function int rather than void. The library declares early
on that void is identical to int, and the word is used when whatever a function returns is
unimportant. The strcmp function is also taken from the library, but the numcmp
function has been added by me. When a qsort call is made, then, its parameters are the start
of the array, the number of items in the array, the size of each item, and the comparison
function. The pointer for the start of any array is always the array name, and the number of
items is always five in this example. The sizes of items are 2 for integers, which store in two
bytes, and 10 for strings, since we have defined strings of ten characters. Remember that
this figure of ten characters includes the ending zero of a string. If when you print your
strings you see two or more joined, this indicates that the zero has been wiped out. The
comparison functions will be strcmp for the strings, and numcmp for the numbers.
All the actions in this program are by now reasonably familiar, but the function numcmp
is new. The qsort routine makes use of pointers, and what is passed to numcmp is a pair of
pointers to integers. These are declared, and then the main action consists of comparing the
integers that x and y point to. These comparisons return numbers 1, -1, or 0 according to
whether the order is incorrect, correct or correct. The order will be correct if the two
numbers are in order, or if they are equal, which is why the three possibilities exist. When
the routines run, then, you will see the lists in their original unsorted form, and then in their
sorted form.
7-13
Linked lists.
Suppose that you defined a structure which consisted of an integer number and a pointer.
Now the most important feature of a pointer is that it can be made to point to something
that may be anywhere in the memory of the computer. Because this is possible, we can
make the pointer in the structure point to the next structure, even if this means a structure
which is not the next one that you enter, or even the previous one. If you make up a set of
structures like this, you don’t need an array. Each structure contains a pointer to the next
structure so that if you can locate the first structure, you can get to any other, swinging like
the legendary Tarzan on ropes of pointers from structure to structure.
head of list end of list
A linked list in diagram form. This is the form of BASIC program lines.
FIGURE 7.11
Figure 7.11 shows in diagram form what this is all about. A sequence like this is called a
‘linked list’.
What advantages would such a set of structures have? Well for one thing, it becomes very
easy to ‘delete’ a structure. All you need to do is to alter the pointer that points to the item
and make it point to the next one instead (Figure 7.12).
Deleting a structure from a linked list.
FIGURE 7.12
Having coped with that idea, how would you reverse the order of two structures?
7-14
Reversing the order of two members of a linked list.
FIGURE 7.13
Figure 7.13 shows the principle in diagram form, requiring three pointers to be changed.
What we have to do now is to see how some of this paper talk can be transferred into a
working program.
struct record!
int daily;
struct record *next;
leash,*f 1 rst,*p;
typedef struct record *rec_p;
main ()
r
X.
int j,x;
p=cast<rec_p)cal 1oc <100,sizeof(struct
record));
first=NULL;
do!
printf("\nToday's number - "> ;
scant (" Xd
p->dai1y=j;
p— >next=f i rst ;
f i rst=p;
p++;
wh i 1 e (j ! =0) ;
rawout(12);
x=rawin() ;
j=i;
p=tirst;
whl1e(pi=NULL)
r
pr l ntt < “ \nXd- V.d “ , j , p—>dai I y) ;
p=p —>next;
J++;
1
J
A simple linked list example. This does not show the supporting functions, and will not run unless these are
added or put in with ^include ?stdio.lib?.
FIGURE 7.14
7-15
Figure 7.14 shows a typical example which is deceptively simple. Only the main part of the
program has been shown here, because when all the necessary functions are added to make
it run, the result is rather massive. In this part, then, we define a structure called record.
This structure contains an integer daily, and a pointer next. What makes next rather
different from any pointer we have looked at so far is that it’s a pointer to type record. In
other words, part of this structure is a pointer to another structure of the same type.
Variable cash is then defined as a structure of type record which contains daily, an
integer number, and next, a pointer. The variables first and p are also defined as pointers
to a record, then the program itelf starts with main(). This starts by declaring j and x as
integers, and then finding a value for the quantity p. We need p as a pointer to where each
structure is going to be stored, and to get this free space we use a function calloc(number,
size). The parameters to calloc are the number of structures that we would want to use,
and the size of each structure. Rather than count up the size of a structure, we can use
sizeof to get this number. The snag in this action is that calloc returns a character pointer,
and we want a pointer to type record. This is arranged by two important lines. One is the
typedef struct record *rec_p which follows the declaration of the structure. We
haven’t used typedef before, because it’s main use is in situations like this along with cast.
The statement typedef allows us to declare that a word represents a data type. It doesn’t
allow us to create any new data types, but it can define a type that will be accepted by the
cast statement. In this example, the word rec_p is being defined as meaning a pointer to
type record, the structure. By using rec_p in the cast statement, therefore, we force the
value of the pointer that is returned by calloc to be of the same type as a pointer to the
structure. You may think that all of this changing of variable type for a set of numbers that
are all integers is rather tedious, but programming can be a lot more tedious in a language
which doesn’t allow conversions!
The next step is to make first point to NULL. There are no values to point to yet, so this
pointer points nowhere. A loop is then set up. The preset limit to size for this set of
structures is set by the value of size that was used in calloc and entry can continue until
you enter a figure of zero, or until the memory is full. You get no warning if you exceed the
limit that has been set by calloc, and if you do exceed it you may suffer no ill effects, or you
may find that the whole program crashes, and your data with it. The freedom that you have
to do as you wish with ‘C’ is paid for by the need to ensure that your program can’t do
anything silly.
Now what happens in the loop is vital to the way that the list is constructed, and you need to
follow it very carefully. I think it’s a lot easier if we can think of numbers for the pointers,
and so I’ll assume that the pointer NULL is zero (true), and that the other pointers will be
40974,40978,40982... and so on (possibly true, depending on machine, and it helps you to
understand it). Let’s take a walk through the first loop round. The value of pointer p has
already been allocated by calloc, and its type is pointer to record, so that we can use
quantities such as p->daily in the same sense as cash.daily. We can imagine that p
carries the number 40974. This will now be used to store the cash amount, p->daily,
obtained as integer j from the scanf line. Notice, incidentally, the blank space preceding
the %d in scanf. This is put in to ensure that the (RETURN)/(ENTER) character does not
cause an endless loop. The pointer quantity p->next for this structure is now made equal
to first, which is NULL. This is the way of signalling that there is no following structure.
7-16
Pointer first is then made equal to p, assumed to be 40974. The value of p is then
incremented using p++. This makes p change from 40974 to 40978, because the structure
contains one integer (two bytes) and one pointer (also two bytes), a total of four bytes.
So much for the first loop. What happens in the second? We pick a new p allocation,
assumed 40978 this time. Once again, we store a cash quantity, and the p-> next pointer is
this time made equal to 40974, the pointer to the previous entry. Pointer first is now made
equal to the current value of p, which is 40978. If you look at these steps in diagrammatic
form, Figure 7.15, you can see that the list is not growing in the way that you might expect.
A
head of list
— 0
end of list
The steps in the formation of the list.
FIGURE 7.15
The ‘top’ of the list is zero, followed by the item that we entered last of all. Its pointer always
points to the next one down, the previous item. The list ends with the one that points to the
first entry. If any more proof is needed, take a look at what the last part of the program,
7-17
following the zero entry, prints out. Enter items like 11, 22, 33 and so on that you can
recognise. When you see the listing it will show 1-0, which is the zero entry that you used
to close the list. After that your other values follow in reverse order of entry, with the most
recently entered item at the top of the list and the first item that you entered at the bottom.
The word next can be rather confusing in program examples like this. It certainly means
the next item in the list, but it’s the next one back simply because there isn’t a next one
forward until you create one!
#def me TRUE 1
ttdefine FALSE 0
#def me NULL 0
#deflne ERROR -1
typedef char*_charptr;
struct header
struct header * _ptr;
unsigned size;
y 5
typedef struct _header HEADER, * HEADER
_PTR;
HEADER base, *_allocp;
#define HEAPSIZE 1000
char sbrk(n)
unsigned n;
r
static char *p , heap CHEAF'SI ZED , *heap_ptr =
heap;
if(heapptr+n>heap+HEAFSIZE)return ERROR
5
p=heap_ptr;
heap_ptr+=n;
return p;
J
char *calloc(n, size)
unsigned n, size;
static HEADER *p, *q;
static unsigned nbytes;
nbytes = (n * size + (sizeot(HEADER) -
1)) / sizeof(HEADER) + i;
if <(q = _aliocp) == NULL) /* no free
list */
'x.
base.ptr = ailocp = q = &_base
5
base, size = 0;
7-18
P = q->_ptr;
while (TRUE)
if (p->_size >= nbytes) /* big en
oagh */
r
if (p->_size == nbytes) q->_p
tr = p->_ptr; /* just right size */
ei se
i /
* split block and allocate tail */
p—>_size —= nbytes;
p += p—>_size;
p->_size = nbytes;
J
_al1ocp = q;
return cast(_charptr) (p+1);
jf
if (p == allocp) /*■ wrapped arou
nd free list */
t
if ( (p = cast(HEADER_PTR> sbrk
(nbytes * sizeof(HEADER))) == ERROR)
return NULL;
p->_size = nbytes;
free(p+1);
P = _aliocp;
J
q = p;
P = p->_ptr;
1 /* end while TRUE */
J
struct record!
1 nt dai1y;
struct record *next;
icash, *i lrst,*p;
typedef struct record *rec_p;
main()
int j , x;
p=cast Creep)cal 1oc(100,sizeot (struct
record));
first=NULL;
7-19
do L
print-f ( “\nToday ' s number - “ i ;
scant ( 11 V.d" ,&_});
p->daiiy=j;
p— >next=t i rst;
t 1 rst=p;
p + +;
3 whi1e (j!=0);
rawout(12) ;
x=rawin <);
j=i;
p=tirst;
wh 1 1e(pI=NULL >
r
v
pr i ntt ( " \n'/.d-7.d" , j , p->dai 1 y) ;
p=p->next;
j ++ ;
J
tree(p > ;
j"
int tree(block)
char *block;
i
static HEADER *p, *q;
p = cast(HEADER_PTR) block - 1;
tor <q = allocp; ! (p > q p < q->
_ptr); q = q->_ptr>
it (q >= q->_ptr (p > q ! ! p
< q->_ptr>) break;
it (p + p->_size == q->_ptr)
f
\.
p—>_size += q—>_ptr—>_size;
p->_ptr = q->_ptr->_ptr;
J
else p->_ptr = q->_ptr;
it (q + q->_size == p>
r
t
q->_size += p->_size;
q->_ptr — p->_ptr;
J
else q—>_ptr = p;
allocp = q;
The complete listing for the linked list program, with all the library functions, courtesy of HiSoft.
FIGURE 7.16
7-20
Figure 7.16 shows the complete listing for the linked-list, using all the library listings that
are included. In these, the word void has been replaced by int. Don’t be downhearted by
the look of a listing like this, because as you can see, the main program, which is the bit that
you write, is quite small. The routines from the library can be taken from the disc and added
in, with no typing required. You can do this by making a disc in which each library routine
is separately filed. You will find this very useful because it allows you to make up programs
without the need to use ^include Pstdio.lib?, which slows down compiling considerably.
You can load in your library routines when you compile by using #include.filename at
the start of the program.
Could we, as a matter of interest, make a list in a different way? When you think about it,
there’s no reason why you shouldn’t. At any point in a list you can direct a pointer to the
next item or to the previous item simply by incrementing or decrementing the p number.
This makes it possible to construct what are called ‘double-linked’ lists with a pointer in
each direction, but programming of that sort is beyond the scope of this book, and if you
need to use it you probably need a lot more memory for your program than you’ll find in the
smaller Amstrad machines. What is more important at this point, then, is learning how to
operate on the lists so that you can search through a list.
struct recordi
1 nt dai1y;
struct record *next;
leash, *i irst,*p;
typedef struct record *rec_p;
main (>
£
int j,x,y;
p=cast(reep)cal 1oc (5,sizeo+(struct re
cord));
first=NULL;
x=0;
dot
x++;
printf ( "XnXsXdXs", " Item ",x," is - " )
5
scan-f ( " Xd " , & j > ;
p->daily=j;
p—>next=first;
-f irst=p;
P++;
> whi1e (j !=0);
rawout(12) ;
—x ;
dot
printf("\npiease select item number.");
pr i nt-f (" XnXsXdXs" , " (range-1 to , x , “ )‘") ;
p=first;
7-21
scanf (" V.d" ,&j>;
if <J >x1i j< I > continue;
el se -C
printf ( ,, \n%sy.d7.5“ , “Item " , j , " is “ > ;
J=x+i-j;
for (y=l;y<=j;y++)
p=p—>next;
J
printf(‘‘Xd“,p—>daily);
>whi1e (TRUE);
Searching through a linked list for an item.
FIGURE 7.17
Figure 7.17 shows how you can search through a list for an item. This is the main part of the
program only, and you will need the same additional functions as the previous listing. You
need to know, of course, what you are looking for, whether it’s the item with a value of 64 or
the first one whose value is less than 9 or whatever criterion you adopt. You need not work,
of course, with integer number values in the structures, as long as each structure contains
some data that you want to use along with a pointer to the next structure. In this example,
the same structures have been used to save extra typing. The aim is to find the entry for a
given identification number, but you could, of course, easily adapt the program to find
whatever feature you wanted of the structure you had defined. The item-finding part has
been put into a loop so that .you can try the options of a number that you know is genuine
and one that you know is not in the list. You can escape in the usual way by pressing the
(ESC) key when you see the ‘please select item number’ message.
There are a few alterations to both parts of the main program this time. An extra integer, y,
is declared, and integer x is used to count the items as they are entered. When entry is
completed, this number is corrected to remove the zero entry which terminated the entry
routine. This also makes it unnecessary to test for the pointer becoming equal to NULL,
though it is better practice to do this also. Following the clearing of the screen and the
decrementing of counter x, the endless loop starts. In this loop you are asked to provide an
item number, which is tested for range. If the item number is in the correct range, the
corresponding item is found. This is not entirely straightforward because of the ‘upside-
down’ nature of the list. The scanf function returns the item number as integer j, and this
is tested for range using continue to ensure that the loop will return to its starting point if
the number is not in range. For a number that is in range, the next step is to adjust the
variable value, using the expression:
j=xTl—j
to ‘invert’ the number. If you have 9 items, for example, and you want the third one, then
this expression gives 9+1—3=7, which is the correct position on the list counting from the
end at which the last item was entered. The corrected number j is then used in a loop to run
through the pointers until the correct item has been obtained. The item value is then
printed in the usual way.
7-22
From this point on, the topic of linked lists starts to get complicated, and you will need to
consult specialised texts if you want to see how to insert or delete items. It’s advisable to
make sure that you have a really firm grip on ‘C’ programming before you attempt such
work, and that’s why I shall not pursue the topic in this book.
7-23
Chapter 8
String functions.
Compared with BASIC, standard ‘C’ seems at first sight rather weak on string handling
routines. This is particularly true when ‘C’ is compared to Locomotive BASIC and to some
of the later versions of Microsoft BASIC, as used, for example, in the MSX micros and also
in the IBM PC. HiSoft C sticks close to the standard in most respects, so that you must rely
on library routines to provide the string handling that you have become used to in BASIC.
When you have moved from BASIC to ‘C’, however, this can leave you wondering how to
implement actions, such as TAB, which you always took for granted with BASIC. You also
have to learn how to use library functions for things like LEFT!, MID$ and RIGHT!, and
the HiSoft manual is particularly helpful in this respect. You will find that a few actions,
notably the VAL action of BASIC, are performed almost automatically and without effort
in ‘C’. The problem, in fact, is very often to make sure that any variable is in the form that
you want! This chapter will be devoted to the use of the string-handling library functions
which help you to bridge the gap. A named ‘C’ function has the great advantage over a
BASIC subroutine in that it is called into action by using its name rather than by the use of
a line number. Because of this ‘C’ functions can be used as if they were new words in the
language, which, from your point of view, they are. All of this is greatly helped by the
appending action of g„filename, which will tack a file on to the end of whatever is in place.
You can also use #include filename to read in library functions from a disc which has the
functions stored separately. The one action that is missed most by ex-BASIC users,
however, is assigning a string variable to some text, as A$=“THIS IS A STRING”. This is
not very difficult to implement in ‘C’, but it requires some planning, unlike its BASIC
equivalent. You should try to make use of string constants, prepared with #define rather
than string variables for messages and other items of this sort. Other string assignments can
often be tackled by the type of combined declaration and initialisation that we have looked
at already.
8-1
#de-fine TAtf “ "
/* eight spaces*/'
tdefine CLS rawout <12i
ttde+ine EOF -1
#define NULL 0
char *gets(s)
char *s;
r
static int
static char
cs = s;
wh i 1 e ( < c =
! =EOF
c;
*cs;
getchar (>)
c!= \n ' )
*cs++ = c;
*c s = 0;
return
< <c==-l && cs=-s)
NULL : s );
char *strncpy(sl, s2, n)
char *sl, *s2;
r
static char *s, c;
s = si;
c = *s2;
whlie (n)
f
*s++ = ( c ? ( c
) ;
—n;
J
return si;
Jf
char *spc(s,n)
char *s;
int n;
f
static char 5pC] = ‘‘
5
/* 20 spaces*/
static char *spc;
spc=s;
strncpy(spc,sp,n>;
spcCn J= \0 ;
/♦needs terminator*/
return spc;
= *s2++ ) : 0
ii
8-2
unsigned strlenvs)
char *s;
S
i.
static char *p;
p = s;
do ; while (*p++> ;
return p-s-1;
y
main ()
r
X.
char sC40D;
CLS;
printf ( " Xny.sXsXsXsXsXs/CsXn " , "A",TAB,"B" ,
TAB,"C",TAB,"B"> ;
printf ( 11 \n7.s\n" , " Your string, please");
gets(s);
pr i ntf ( " \n'/.s7.u7.5\n “ , "consists of " ,stri
en(s),“ characters.“);
printf ( "\ n'/.sXs7.s7.s'/.s7.5" , “This" , spc (s,5) ,
“ 1 s",spc(s,3),"spc“,spcis,4));
printf ( "7.s”/.sy.s" , "in", spc (s, 7) , "action " ) ;
A few string actions, including fixed tabbing, strlen, and spc.
FIGURE 8.1
The set of string actions starts with Figure 8.1.1 could have shown all of the string library
functions in one long program, but I have split these into sections because it’s easier this
way to select the actions that you need most frequently. Be warned, though, that these
functions are in ‘skeleton’ form. They are completely lacking in any mugtraps, so that
misuse can crash your programs. The aim is to provide simple functions, with no frills,
which you can use as starter packs for your own routines. The simplest string action
consists of preset tabbing. By defining a constant TAB of eight spaces, we can place the
word TAB into a printf statement and automatically space words or numbers. Remember,
however, that this does not over-ride any field specifications, and that typing
printf(“/n%s%s%6d”,“Total”,TAB,j) ; where j is an integer will produce more than
eight spaces because of the field number ‘6’. If you field your numbers correctly, however,
this will not be a problem. The use of this preset T AB, which can, of course, be of five spaces
or any other number you please, gets over a lot of the display problems that ex-BASIC
programmers encounter when they start to use ‘C’. In this sense, TAB is being used more
like the TAB of a typewriter than the TAB ofBASIC. The action is closer to the instruction
SPC(8) which is allowed in some versions ofBASIC, or to the way that the comma is used
in BASIC. Remember that ‘C’ can use the field numbers for its tabulation, and these are
often much easier to use than the BASIC TAB instruction. The TAB in this example is
produced simply by using ^define, with TAB made equivalent to eight spaces. In addition,
CLS is defined to be equivalent to rawout(12) to clear the screen.
8-3
The next action in the list is the strlen function. This is a HiSoft library function which
produces a value for the number of characters in a string up to the final zero. This is not the
same as the total length of the string, because the zero is not counted. The string name is
passed as s, defined in the header as a pointer. Another pointer, p, is then declared and set
equal to the value of s so that at this instant, both s and p are pointing to the start of the
string. The do..while loop then increments p until it points to a zero, the end of the string.
The length, including the zero, is then p-s, and subtracting another 1 gives the length not
counting the final zero. A lot of other functions can make use of strlen, so that this function
is one of our ‘bankers’, one that you should keep on a disc and not delete from this program
unless you are sure that it is not used in later versions.
The function spc(s,n) is one that will return a specified number of spaces. This is not one of
the library functions, though it depends for its effect on a library function which copies one
string into another. This function has been written so that it returns a string, and can
therefore be included in a printf line. It could equally easily have been modified so that it
printed the spaces itself, and returned nothing. It’s up to you to choose how you want to use
a function of this type, but the use of a function that returns something is often more
flexible. In this case, the variables s, a string to be returned, and integer n, the number of
spaces, will be passed to the function. These are declared in the header, with s as a pointer to
the first character of the string. Following the curly brackets, the string sp is declared and
assigned to twenty spaces. This means that if you try to create more than 20 spaces, you will
get very odd results! The pointer spc is also declared, and assigned to s to pass the string
back. The library function strncpy is then used to copy the correct number of spaces into
the string spc. This library function will not put in the zero at the end of the string, and this
has to be done in a separate line. The string spc is then returned.
In the library function *strncpy(sl,s2,n), a number of characters n from the string s2 are
copied into a string si. If the string s2 is short, shorter than ‘n’ characters, it is copied
complete, with its ending zero. If, however, the string s2 is long and the number n selects
only part of the string, then the final zero is not copied. You have to be certain before you
make any use of this function (and the similar strcpy) that the string si has been defined as
long enough to take whatever is being copied into it. As usual ‘C’ lets you do whatever to
want to, even if it’s likely to crash the whole program! In the strncpy routine, the string
pointers are declared in the header. The integer n is not, because the declaration of an
integer in the header is optional in HiSoft C, though it’s very desirable to put in the
declaration if only as a reminder. You’ll find some integers declared, others not declared in
the HiSoft C library, depending on whoever wrote the original versions. At the start of the
function, pointer s to char, and char c are declared, then the statement s=sl sets the local
pointer s to the start of the string into which characters are to be copied. Character c is then
assigned to the first character of the second string, s2. A loop then starts which will run as n
is counted down until the value of n reaches zero. In the loop the value assigned to *s, the
string copy character, will either be the character copied from s2, or zero. If the current
character c is not zero, then the character from s2 is used, else a zero is put in. Note,
however, that the zero is copied only when a zero exists in the second string. The string
pointers are incremented, and the number n is decremented. If the loop finishes because n
has been decremented to zero, then no zero is copied. Looking at this loop, incidentally, you
might be tempted to rewrite the whole routine in the form that is shown in Figure 8.2.
8-4
char *strncpy(si, s2, n)
char *sl, *s2;
i
static char *s;
int j;
s = si;
tor (j =n; j >0; j —)
*s++=(j ? < *s2++) : 0 > ;
return si;
A rewrite of strncpy which is more suitable for these applications.
FIGURE 8.2
This uses a for loop to carry out the action, and has the advantage, for our purposes, of
always putting in the terminating zero when the whole string has not been copied. This, of
course, is not a replacement for strncpy except for this type of use. The more general
library routine is the one to use when you want to copy entire strings, and when the end is to
be signalled by the zero character normally rather than by the count.
The main program illustrates the use of this ‘starter-pack’ of string handling functions and
functions. The screen is cleared, using the defined CLS, and the letters A, B, C, and D are
tabbed across the screen, using the constant TAB. A figure of eight spaces is a particularly
convenient one for most purposes, but you might want to use 5 for separating integers
particularly if the field size is small. Remember that this TAB, unlike TAB in BASIC, is
really a spacing command. In other words, it sets the amount of space between items rather
than the absolute position of items on the fine. The next three lines illustrate the strlen
function being used simply to print out the length of a string. This would not normally be of
interest but you might want to make use of strlen to decide whether a string was too long to
use, or in other string handling actions. Finally, the last two lines illustrate the use of spc.
This, remember, is a function which returns a string so that it can be put into a printf
statement like any other string.
#de+ine LLS rawout(12)
#define NULL 0
#define EOF -1
char *gets(s)
char
static int c;
static char *cs;
cs = s;
while ((c = getchar(>)
!=EOF && c!='\n')
*C5++ = c;
8-5
*cs = 0;
return
<<c==-l &S( CS==S> ?
NULL : s >;
n
j'
main (>
f
int x,y;
char sL403;
CLS;
printf("\nPlease type your text\n") ;
gets(s);
printf(“\nand the x,y, numbersXn");
scant (" V. d %d“,&x,&y);
/♦should test these*/
printat(x,y,s);
printat <x,y,s)
int x,y;
char *s;
f
K.
rawout<31>;
rawout (x);
rawout <y>;
pr 1 ntf( “Xs“, s);
Achieving a PRINTAT action.
FIGURE 8.3
A function which resembles the PRINTAT facility of some computers is illustrated in
Figure 8.3. In the BASIC of many computers, PRINTAT is followed by X and Y co¬
ordinates and then by a string which is to be printed. This allows a string to be printed
anywhere on the screen, irrespective of the normal position of the cursor. PRINTAT can
be implemented very easily in HiSoft C thanks to the provision of rawout() in conjunction
with the Amstrad codes, and the PRINTAT action is almost as fast as normal printing. The
function makes no use of any other library function, because rawout is a built-in function
of HiSoft C. The function requires three parameters, the X and Y positions (integers) and
the string text. In this example, no attempt has been made to test the values of x and y, and
if you seriously wanted to use this function, you should really include some limits on the
numbers. In the main program gets(s) is used to get the text, and scanf is used to get the
numbers. The spaces in the scanf specifier portion are deliberate, in case you want to use
the program in a loop. You can enter the numbers either by typing one, then
(RETURN)/(ENTER), then the other (and (RETURN)/(ENTER)), or by typing the
numbers separated by a space and then pressing the (RETURN)/(ENTER) key. This latter
method is much quicker, but you have to remember to get out of old BASIC habits - no
comma must be used.
8-6
The use of printat() gives you a lot more freedom over the placing of text on the screen,
and saves having to make each positioning of text into a major exercise. Useful additions
when you don’t necessarily want the complete freedom of printat are vspc and cent,
illustrated in Figure 8.4.
#de+ine lLS rawout(12)
#detine NULL 0
#def 1 ne EOF -1
char *gets(s)
char * 5 ;
r
static int c;
static char *cs;
cs = s;
while ((c = getchar ())
!=EQF && c!='\n')
*cs++ = c;
*cs = 0;
return
<<c==-l cs——s) ?
NULL : 5 );
unsigned strlen(s)
char *s;
r
V
static char *p;
P = s;
do ; while (*p++);
return p—s—1;
■»
J
main ()
f
X.
char s1201;
int v;
CLS;
printf ( " \n7.s\n" , “Please type the title.,
gets <s> ;
printf("\nXs\n",“and the size of vertica
1 space">;
scant (" 7.d" ,&v>;
printf("\nXu",v);
CLS;
8-7
cent(s);
vspc(v);
print+("%5","thi 5 is the next text line"
);
J
cent(s)
char *s;
r
■H.
int x , j;
j=strlen <s);
x= <39—j>/2;
-f or (j = 1; j < =x ; j ++)
rawout(9>;
printt("%s“,s);
l
J
vspc(v)
int v;
r
i nt j;
tor(j=0;j<=v;j++)
rawout(10>;
rawout <13);
The vspc and cent functions.
FIGURE 8.4
Function vspc prints, as the name suggests, a number of empty lines, so spacing your text
vertically. The cent function will centre a title in its line. As always, you have to be careful
of string dimensions. For the cent function, it’s better to work with character arrays of no
more than 40 in length, because you wouldn’t want to have two-line titles centred. In this
example, the dimensions of s have been limited to 20.
The vspc function takes as its parameter the number of lines that you want to space
vertically, and a loop counts out an equal number of r awout( 10) statements. These have to
be followed by a single rawout(13) to place the cursor to the start of the line, otherwise it
will be left wherever the printing of the title left it. The cent function takes a title, a string s.
This has been defined as an array of up to 20 characters, and function strlen has been used
to measure the length of your title and assign it to integer j. The centre position is given by
using the formula - the figure of 39 has been used rather than 40 (characters in a fine),
because the left margin on the monitor makes a centered title look too far over to the right
when using 40. The action of rawout(9) is to tab the cursor to the right, and since this is
done in a loop the correct horizontal position is reached.
8-8
Left, right and middle.
The BASIC functions LEFTS and RIGHTS can be replaced to some extent in HiSoft C by
the use of some of the printing functions or strcpy. The manual, for example, suggests the
use of printf(“%2s”,strncpy(s,string^)) as a replacement for LEFTS(string,2) and
puts(string+6) as a replacement for RIGHTS(string,6). These actions apply to printing
only, however, and they don’t allow you to do what you can do so easily in BASIC, that is to
assign part of a string to another string variable name. The next few functions, then, are
aimed at providing this action, and also with providing the equivalent of a MIDS action.
♦define CLS rawout(12)
ttdefine NULL 0
♦define EOF -1
char *gets(s)
char * 5 ;
static int c;
static char *cs;
cs = s;
while <(c = getchar(>>
!=EOF && c!='\n )
*C5++ - c;
*C5 = 0;
return
((c==-l && C5=-S) ?
NULL : s );
J
char *strcpy(dest, source)
char *dest, *source;
L
static char ^result;
result = dest;
while (*resu.lt++ = *source++) ;
return dest;
Y
J
char *left(s,n)
char *s;
int n;
Y
char *p;
p=s;
wh Ue(n)
f
p++;n—;
8-9
*p=0;
return s;
■fc
J
char *right<s,n>
char *s;
int n;
r
X
while(*s> s++;
whlle(n)
5—; n—;
t
J
return s;
jr
iTia in()
X
char s£403,strC40J;
CLS;
print-f ( " \nPl ease type some text\n ,; );
gets(s);
/♦can use tgets to restrict size*/
str cpy tstr,s>;
phi ntf ( “\nXs7.s\n“ , “The left<3> is “,ie+t
tstr ,3i);
/*s is still original string*/
str cpy (str , s; ;
pr 1 ntf ( " \n7.s7.s\n " , “ 1 he right (3) is “ ,rig
ht(str,3>>;
Functions to provide the left and right string slicing actions.
FIGURE 8.5
Figure 8.5 tackles the left and right problem. Now these are ‘skeleton’ functions in the
sense that they do just the minimum that is required. There is no checking to see if the
numbers that you use are sensible, and a standard string length of up to 40 characters has
been assumed. What is being done is to use a pointer to shift either the starting address or
the finishing address of the string. This means that the string which is supplied has its
pointer temporarily altered by the function, and the new pointer position returned.
In the examples, a string which is an array of up to 40 characters is chopped and the
chopped portion is assigned to a string of the same name (with the same pointer). Once
again the listing shows the header section as well, and you can see that functions gets(s)
and strcpy(dest,source) have been used. The strcpy function is used to ensure that the
same string is present for both demonstration runs, because the function left puts a zero
into the string.
8-10
The function left requires the parameters s, the pointer for a string of up to 40 characters,
and the position integer n. Pointer p is used as the position indicator for the characters in
the string, and the action consists of shifting this pointer to the end position of the sliced
string, n characters on, and then placing a zero following the nth character. The function
then returns the original string pointer to the start of the string, but since the zero has been
inserted after n characters the string will now appear to be of this length. The function is
used by typing left with the correct parameters, either to give a new assignment to the
existing string, in an assignment (using strncpy) to another string, or as part of a printf
statement. Note that the function allows no protection from a silly number, like
left(“Hi”,5). In a ‘skeleton’ function like this protection is omitted, but for a program
which might be used by anyone who was unaware of the restriction, you would need to
carry out a check based on detecting the end of the string in the while loop. The right
function carries out a rather different action, using only the main pointer s. First of all s is
advanced to the end of the string by a while loop, which tests for the zero character at the
end. When this position is found the pointer is decremented, along with the counter n until
n reaches zero. This places the pointer to the correct number of characters away from the
last (non-zero) character in the string. This time, what is returned is the altered pointer.
Like left(s,n), the right(s,n) action can be used as part of printf or independently to
change an assigned string. Once again, there is no protection against reading to the end of
the string and then counting back too many characters, probably into another string. As
before, if you want protection you will have to add it in the shape of detecting a zero
character. A useful alternative form of protection for both types of slicing function is to
compare n with strlen(s) before attempting to slice, and returning with the string
unaltered if the specified number would cause problems.
The equivalent of the BASIC MID$ action is carried out by the MID function in Figure 8.6.
ttde+ine CLS rawout(12)
#detine NULL 0
#define EOF -1
char *get5(s)
char *s;
static int c;
static char *cs;
cs = s;
while tic = getchar i i >
!=EOF && ci= \n >
*cs++ = c;
*cs = 0;
return
( ic==-l ii&r. cs==5) ?
NULL : s );
8-11
char *mid(s,pos,ieni
char * 5 ;
int po5,ien;
L
char *p;
whi1e(po5-l) C
s++;pos—;>
P=s;
wh ile(len >i
P++;1en—;J
*p- 0 ;
return s;
J
main t )
(char sL4(3J;
CLS;
printl(“\nPlease type some text\n">;
printt(“\n(at least S characters.)\n“);
gets(s);
pr i nt-f < *■ \n‘/.s7.s" , “The mid(s, 3, 4) is “,mid
(5,3,4));
The mid action, corresponding to BASIC MID$.
FIGURE 8.6
In this function once again, s is the pointer to the string which is to be sliced and pos is the
position of the first character that you want to copy. The integer len is the number of
characters that you want to copy. Some MID$ instructions in BASIC use this second
number as another position number, but in this particular function it is used in the same
way as in the MID$ of Amstrad BASIC. The action is the same type as has been used for
left and right, and it consists of incrementing the string start pointer s to the position that
is given by pos. A new pointer, p, is then placed at the same address by using p=s, and this
pointer is then advanced along the string using len as a counter. The zero is then placed at
the end of the sliced portion of string, and the new starting position is returned as s. Like the
previous functions there is no protection here against silly numbers, and it’s a good exercise
in logic to devise a protection against either the impossible starting position or the
impossible length of slice, or both.
Another useful function which is provided on many computers, including the BASIC of the
Amstrad machines, is STRINGS. This will make up a string of a number of identical
characters, with the number and the character specified. For example, in BASIC,
X$=STRING$(20,“*”) will make X$ a string of twenty asterisk signs. This is a quite
simple action to achieve in ‘C’. In addition, most varieties of BASIC have the STR$
function. This converts a number variable into a string variable; something that is quite
often needed in BASIC. ‘C’, with its very flexible rules about data types, is pretty good
about conversions, but mainly in the other direction.
8-12
♦define LLS rawout(12)
♦define NULL £3
♦define EOF -1
char *gets(s)
char *a;
i
V
static int c;
static char *cs;
cs = s;
while ((c = getchar())
!=EOF && ci= \n'>
*cs++ = c;
*cs ‘= 0;
return
< tc==-l && cs==s) ?
NULL : s );
char *fl11(s,n,c>
char *s,c;
int n;
r
V
while(n) {
*s++=c;
n—; >
*s=0;
return s;
J
char *str(s,n >
char *s;
l nt n;
sprintf(s," Xd“,n>;
return s;
■»
J
main (>
r
char sC403;
int x;
CLS;
x =2345;
str(s,x >;
printf ( 11 \n7.s - Xs\n" , "String version is
11 ,s> ;
till (s,20, '* >;
printf (“ \n'/.s 1 ' , s) ;
J
How to fill a string with a character, and the conversion from number form to string form.
FIGURE 8.7
8-13
Figure 8.7 shows the STRINGS equivalent, fill, and the str functions. Note that the dollar
sign cannot be used as part of a function name in ‘C\ The fill(s,n,c) function emulates the
STRINGS action, but you have to remember the method of specifying the character. The
parameters that have to be passed are the string pointer (name), the number of characters
and the character that is to be used for filling the string. As usual, the function counts the
number of characters into the array, using *s++=c to put a character c into the string at
pointer number s, and then increment the pointer. By using n as a counter, the correct
number is put in place. The ending zero is then added by using *s=0, and the original string
starting address is then returned. Remember that the use of s in the function does not affect
the string pointer that is passed to the function, though we might affect the string
characters by use of the pointer. This function is called by typing fill, followed, in brackets,
by the string name, the number of characters and the filling character. Note that only one
character can be specified in one fill instruction, and that the character is surrounded by a
single quote each side, not a double quote. Remember, ‘a’ is a character, “a” is a string. The
result of fill is the string s filled with the characters. There is no check on the number that is
passed to this routine, so that it would be possible to stop the program by specifying a
number which was greater than the dimensioning of the array. Since these are skeleton
functions, you can fill in the fleshy bits for yourself.
The integer to string a function is even simpler. The string name and the number are passed
to a sprintf statement, which carries out the conversion. There is really little point in
making a separate function out of this one, and you would normally make direct use of
sprintf. Converting from a string form of a number into the form of a number variable can
also be carried out by a built-in function, sscanf. For example, if string s[]=“123” and n
is declared as an integer, then using:
sscanf(s,“ %d”,&n);
will assign the number 123 to variable n. This carries out the action of VAL in BASIC.
Concatenation and insertion.
Concatenation means joining two strings together, and it is achieved in most dialects of
BASIC by using a + sign between strings. There is no similar instruction in HiSoft C, but
there is a very useful strcat function in the library and we can easily write a skeleton
function for the insert action. Insertion in this sense means that one string is added to the
other at some intermediate position. You might, for example, insert DIME into SENT so as
to get SEDIMENT, though it’s hardly likely to be something that you would do very often!
Nevertheless, both concatenation and insertion are useful actions at times and they can be
provided with the functions of Figure 8.8.
i ne CLS r awout < 12)
#de+ine NULL 0
idetme EOF -1
char *strcat(base, add)
char *base, *aad;
8-14
static char *dest;
dest = base;
while <*dest) ++dest;
while (*dest++ = *add++>;
return base;
J'
char #ins(sl,s2,n)
char *5l,*s2;
i nt n;
L
char *q,*p,tmp1201,*t;
p=sl;
whi1e <n—)p++;
q=p;
t—t nip;
while «*p> *t++=*p++;
*t=0;
whi1e(*s2)*q++=*s2++;
t=tmp;
whi1ei*t ) *q++=*t++;
return si;
1
J
main t)
r
'i.
static char si L20J = "sent‘‘;
static char s2C 3 = ,, dime” ;
static char s3C201 = 1, sent ‘;
static char s4C]= n dime";
CLS;
print! ("\ny.5‘/.5\n" , "strcat gi ves " ,strca
t(si,s2));
print! ("\ny , .s7.s\n" , "ins gives “,ins<s3,s
4,2) ) ;
Concatenation and insertion of strings using functions.
FIGURE 8.8
Looking first at the main program, two sets of strings are declared and initialised. One pair,
si and s2, is concatenated by using the strcat function which has been copied from the
library. The other pair, s3 and s4, is subjected to the insertion routine. In this routine, two
strings and a number are passed. The number decides how many characters of the first
string are allowed before the second string is inserted. In this case, since n=2, the result
should be se from string s3, then dime from string s2, then the remaining nt from string
si, giving sediment.
8-15
The strcat action is fairly simple. Pointer dest is assigned to the start of the first string and
the first while loop then moves the pointer to the last character in the string, the one
preceding the zero. The next while loop then transfers characters from the second string
on to the end of the first one, overwriting the zero at the end of the first one. This continues
until the final zero of the second string has been assigned, and the program then returns the
original starting pointer. Since the characters have been changed in the routine, the string
that we passed to the routine is now changed, so that we can’t use string si in the second
part of the main program.
The action of ins is slightly more complicated. For that reason, the programming has been
left in a simple form rather than compacting it. The first action is to set pointer p to the start
of the first string. A while loop then advances this pointer by the number of places given by
the number, n. Note that there is no error-trapping here, and you might want to ensure
that a silly value of n could not corrupt the string. The pointer q is then used to hold this
position, the position of insertions, and pointer t is used in another while loop to put the
remainder of the first string into another string variable, tmp, which is dimensioned to 20
characters. This is another place where you might want to put an error trap to prevent this
string from being over-filled. The zero is placed at the end of this string (can you think of a
better method having seen strcat in action?), and then characters are copied from the
second string. Finally the remaining characters from the first string are copied from tmp,
and the pointer to the first string is returned.
Other string and character functions.
The HiSoft manual illustrates a method of obtaining the INSTR$ action of BASIC, and
also for the more usual action of obtaining a starting position for a string. The normal
action of Locomotive BASIC is to get the position number of one string inside another. For
example, using INSTR(JOHN SMITH,SM) should give the answer 6 since the ‘S’ of
‘SMITH’ is the 6th character.
#del me CLS rawou.t (12)
hdetine NULL 12
#det l ne c.Uh —1
int strncmptsl, s2, n)
char *sl, *s2;
if <!n) return
while t*5l == *s2)
l + ( I
l f ( !
++s 1;
return *si
*si ) return
- f t ) uV E'cik 5
r "ft 3 xl \
— *
U;
8-16
unsi qned stir I en \ 5;
char *5;
static char <pi
00 5 wiule (*p 1 j
return p-s-i;
j
int mst i>Tsain, bud )
c h ar * maxn,* sub;
in l xen =
char *5;
i. en=str i en ( bud > \
5-illdl n 1
Li o L
it (. ! strncmp ifflai n ? bud , i en ) } r eturn (mai n—s
J ‘ %
j wn lie i *+•+< Tia 1 n > ;
return (ZS;
main (>
static ctidr str L 3 = “ * eststr i ng“ ;
static char bi tt ] = ,, tst “ ;
(jrintt i" \n‘/. 5 /.d“ , “Position is ",instistr,
bit/);
J
A modification of the library function for the INSTR action.
FIGURE 8.9
Figure 8.9 shows an alternative version of the library function which will always provide
the position number, or zero if the string cannot be found. There is no provision in this
routine for starting the search at a given number of characters along the main string, but
this is something that you can easily add if you want it All that has to be added is the integer
number, which is then added to the starting pointer of main. The'action of the routine
which is shown depends heavily, like the corresponding HiSoft library routine, on the use
of strlen and strncmp. The length of the small string is obtained as the integer len, and
the pointer s is set to the start of the main string. The do..while loop makes a test and
continues until the test succeeds or until the end of the main string when the zero character
is detected after incrementing the main pointer number. The test is that strncmp(main,
sub,len) is not zero. The strncmp function will return a zero unless two strings of the
same length len are identical. The loop will therefore cycle round, advancing the starting
character until len characters are identical in the two strings. If this is found, then the
return is of(main-s), the difference between the incremented pointer and the original value
for main. This is the position number for the first character of the small string. The only
8-17
safeguard in this routine is that a zero is returned if the end of the main string is found
before a match can be found, and this causes a problem if the two strings are identical. The
character count in this example starts from zero, so that identical strings give the same
result as no match. If this is likely to cause trouble, use a return(main+l-s) in the inst
function.
In addition to the string functions that we have looked at in detail, there are several others
which are sometimes useful. Function strchr will give a pointer to the first place in a string
where a given character occurs. The manual illustrates the main use for this function which
is to check that a character belongs to a set, such as the letters in a Roman number, the
numbers of the weekdays, or whatever. You can also see from the example that this is one of
the actions which Pascal does rather more neatly than ‘C’! There is also a similar function
strrchr, which returns the last position of character c in the string. This can find, for
example, the surname in a name that consists of more than two parts by getting the last
space in the name. Three other functions are rather more exotic. Function strpbrk
compares two strings, and finds the first place in ‘stringl’ where any characer from ‘string2’
occurs. This one is of use if you are looking for the occurrence of particular letters for some
reason. The strspn and strcspn functions both look at the start of a string, si, and
compare it with a string s2. If the first few letters of si contain letters from s2, then strspn
returns the number of letters. The function strcspn, by contrast, returns as many letters at
the start of si as are not in s2.
In addition to these string functions there are many useful functions which operate on
characters, one by one. The most useful of these character functions are built-in, and they
consist mainly of character analysing actions. Looking at the built-in functions to start
with, you can use isalpha to decide if a character is a letter, upper or lower case, and
isdigit to decide if a character is a digit. The other three built-in tests are islower to test
for lower-case, isupper to test for upper-case, and isspace to test for ‘whitespace’.
Remember that a ‘whitespace’ character can be the spacebar character, the newline, or the
tab character. Also built in are two character changing functions, tolower and toupper,
which change the case of a character. There are also several tests which are not built in: for
alphanumeric characters (letters or digits); for ASCII codes; for control characters; for
graphics characters; for printing characters; or for hex digits. All of these have their uses
particularly if you want to parse a phrase, of which a little more later. Meantime, Figure
8.10 illustrates some of these tests and conversions in action.
#detine Ci_B rawout (12)
fcde-fine NULL 0
#detlne tOF -i
unsigned strien<s)
cnar *s;
l
static char *p;
P = si
do ; winie c*-p ■*■+) ;
return p—s-1;
char *cap tna.iT*ei
char * n a me;
8-18
i_§ idr" ;
int j,Ien;
p=name;
1en=strien(name > ;
wfc 1 1 e ( )
't.
it <p——ri-sme) *p = toupper t*p> ;
it ( 1 sspace(*p)> * tp + i)=toupper(*(p+1)>;
pt+ ;
return name;
Dial n ()
static char nameLD=“John smith williams"
k
LiL-O i
printt ( “ \nItsXs\n" , " Name is ",name);
pnntt 1"\nXs/.s\n " , "Name is now ",cap(nam
e) ) ;
Illustrating the character tests and conversion functions.
FIGURE 8.10
The aim here is to analyse the letters of a name and convert correctly to upper case. The
principle is that the first character ought to be in upper case, and any character which
follows a space should be in upper case. Perhaps you might like to try altering this routine
so that it could deal with names that contained a hyphen? The important points, however,
are to see the tests in action, and how the changing functions are used.
The main intended use of all these tests, however, is in writing interpreters and compilers.
The ‘C’ language was developed just for such purposes, and this accounts for the number of
functions which analyse and work with characters. One of the important features of a
language interpreter or compiler is ‘parsing’. This (for the sake of readers younger than 40)
means separating the parts of a sentence to explain what each part does. In a BASIC
statement such as PRINT C, for example, PRINT is the word that describes the action, and
C is the name of a number variable. A parser function can deal with a piece of text like this
in at least two ways. One way is to go through the letters, looking for the space and then
splitting the phrase into its two parts. If this is done, then a statement such as PRINTC will
be rejected because the space is missing. The alternative is to use the strspn type of action
to detect the PRINT, and then separate the ‘C’ by using the number from strspn. This is a
more common method, and in most BASIC interpreters a code number (the ‘token’) is then
substituted for the action word. In this book, there simply isn’t space to start considering
the fascinating work of writing a compiler or interpreter. If you are interested, I can
thoroughly recommend the book ‘Writing Interactive Compilers and Interpreters’, by P.J.
Brown (John Wiley). Apart from anything else, it’s a thoroughly readable, interesting and
useful book for anyone with serious programming interests.
8-19
Chapter 9
Graphics, sound and BASIC conversion.
The excellent graphics and sound capabilities of the Amstrad micro would be wasted if it
were not possible to make use of them from HiSoft C. This is done by making use of a
library of functions which carry out the graphics and sound actions. In addition there is
another library which carries out actions such as setting function keys, which are available
in the BASIC of the Amstrad machines. Since the use of these graphics and sound
instructions in BASIC is well documented elsewhere, I shall not take up space in this book
with them. In general, all of the normal instructions that Locomotive BASIC uses for
graphics can be implemented in ‘C’ with a few slight changes in syntax and one major
enhancement. The SOUND statement of BASIC, however, has been abandoned in favour
of a scheme which is closer to the methods used on the IBM and MSX computers (and
Dragon, if anyone remembers!).
As an illustration, we’ll look at a simple piece of graphics executed by ‘translating’ a BASIC
routine.
10 MODE 1
20 CLG
30 F UE N— 1 i 0 D0
40 MOVE 320,200
50 DRAW END(1)*fc>39,END(1)*399,END <1)*4
60 NEXT
A graphics routine in BASIC.
FIGURE 9.1(a)
9-1
#include stdio.h
/♦Simple graphics*/
main ()
L
int n;
rawout(4 )5 rawoutt i ) ;
/♦set mode 1 */
G_clear_window() ;
/ *L,LG*/
tor (n=l;n<=50;n++)
G_move_absol ute (320,200) ;
/*MQVE(320,200)*/
G_set pen (rand (> 7.4) ;
/♦random colour*/
G_1 ine absolute (rand <) 7.639,rand (> 7.399) ;
/♦HOVE*/
J
rawin (> ;
/♦hold pattern*/
j
#inciude ?basic 2 .lib?
#inciude ?stdio.lib?
The conversion into ‘C’, using the basic2.1ib routines.
FIGURE 9.1(b)
The BASIC version is shown in Figure 9.1(a), the ‘C’ version in 9.1(b). The BASIC
statements have been ‘translated’ into ‘C’ functions and statements, using the HiSoft
Manual list of equivalents. Because these call for functions from the stdio.lib and
basic2.1ib, the header stdio.h must be placed first in the listing and the other library
names put at the end so that the compiler can select functions in the usual way. Most of the
translations are fairly direct, but some changes are needed to random numbering. The
random number function of Locomotive BASIC gives a random number which is always
less than 1. The rand function in the stdio.lib generates a number which is an integer, and
which is not so random as the RND ofBASIC. You can see this when you run the program,
because the pattern is always the same. Even if you try to alter the randomness by using
srand(n) at the start of the loop, you’ll find that the patterns repeat. This is a consequence
of not being able to use floating-point numbers, which prevent the ROM routines for
random number from being effectively used. You can, however, make use of other random
number routines. In place of the RND(1)*399 type of routine that is used in BASIC, the ‘C’
version uses rand()%399 to create a ‘random’ integer and find its modulus with 399 - in
other words, to find a remainder between 1 and 398.
Any program in BASIC which deals with graphics can make use of a ‘C’ translation of this
type. It’s much more interesting, however, to make use of the new routines that ‘C’ permits.
9-2
The main new graphics routine is draw(), and its action will certainly not be familiar to
Amstrad owners though it is well known to users of the MSX machines. Knowing the
action is one thing, knowing the syntax is another. Like the function which is available for
sound, the draw function uses position numbers which don’t bear much similarity to the
numbers that are used in the BASIC of the Amstrad machines. In addition, the numbers
that are used must be expressed in a way that is certainly not familiar to many
programmers nowadays, - octal. There’s more about octal scales in Appendix A, but to
introduce the draw action, take a look at the listing of Figure 9.2.
tinclude stdio.h
#def me CLG i nl i ne (0xcd , 0;<bbdb) ;
main (>
char ^string;
CLG;
stri ng="S5 m\0\0\217\l I\177\2\0\0\0" ;
draw(string);
rawin ();
j
#inciude T'basic2. i ib?
An illustration of the draw function in the basic2.1ib library. Note that the numbers are in the octal scale.
FIGURE 9.2
This defines a string:
“0 m \ 0 \ 0 \ 217 \ l 1 \ 177 \ 2 \ 0 \ 0 \ 0”
which is then used in a draw function. The result of all this effort is a horizontal line across
the screen, half way up and extending all the way across. How does the string cause this
effect?
The answer is that the letters act as commands for graphics actions. The complication is
that each letter has to be followed by numbers, anything from zero to four numbers, and in
octal. The three commands that are illustrated are 0 (the zero byte), 1 (ell) and m. The 0
command causes the graphics cursor to go to the bottom left-hand comer, the (0,0)
position. This command needs no numbers following it. The 1 command moves the cursor,
but without leaving any trace. It needs four numbers following it. The m command letter
means move, and it draws a line. It also needs four numbers following it. The range of
numbers is the same as the standard Amstrad BASIC range. The range ofX numbers is 0 to
639, the range of Y numbers is from 0 to 399. These are denary numbers, and the ones that
follow the commands are not. The conversion is by no means simple, because each number
has first to be converted into two bytes, in low-high order, and then into octal.
9-3
The conversion is carried out in stages. First of all, if the number is negative, it has to be
subtracted from 65536. If the position number that you want to convert is then less than
256, then it fills only one byte of memory with zero in the upper byte. A number such as 190,
for example, can be written as 190,0 - meaning a low byte of 190, high byte of zero. If the
number is greater than 255 but less then 512, then its high byte is 1 and its low byte is
obtained by subtracting 256. For example, the number 320 has a high byte of 1, and a low
byte of320-256=64. It would be written in two byte form as 64,1. If the number is greater
than 512, it has a high byte of 2, and the low byte is the number less 512. For example, 600
has a high byte of 2, and a low byte equal to 600-512=88, and would be written as 88,2. If
the position number started off as negative, then subtracting from 65536 produces a large
number and it’s easier to find the high/low bytes using the alternative procedure. The high
byte is number/256 (integer division) and the low byte is number%256 (the modulus).
Either procedure gets numbers into two-byte form. The next step is to get them into octal
form. Take a number such as 88, divide it by 8, which gives 11 and no remainder. Write
down the remainder, which is 0. Now divide the 11 by 8, getting 1 and a remainder of 3.
Write down the 1, then the 3, then the final zero, and you have 130, the octal version of 88
denary. If you find this a nuisance, then you have two options. One is to buy an octal
calculator, such as the TI® LCD Programmer, the other is to make use of the program in
Figure 9.3.
/*L)ctai bytes*/
unsigned oct(n)
unsigned n;
L
unsigned y,x,b;
y=x =63; b = l;
dot
l -f (y! =0) n=y;
x+=b*(n%8>;
b*=i0;
y=n/8;
i whiie(y >=8;;
x+=b*y;
return x;
J'
unsigned negit(inp)
char *inp;
f
l
unsigned j;
inp+=i;
sscanftinp," Z.d’‘,&j);
return 165536-j > $
J
main (>
r
t
unsigned j,io,hi;
char inpL53;
dot
9-4
printf(“\nDenary number — Xn">;
scant (" 7.s",inp);
if <*inp== — > j=negit( 1 np);
else sscantUnp,“ 7.d“,5o>;
lo=j 7.256;
hi= j 7256;
printf<"\n 7.s \\7.d \\7Ld \n“, “Octals ar
e “,oct(io>,octthi)>;
j whl1e(j!=0);
J
A program for performing conversions from denary to octal, positive or negative.
FIGURE 9.3
This carries out the conversions for any number in the approved range of 0 to 640, positive
or negative - you enter the number when asked, and you get a printout of the two bytes in
the correct order. The bytes are written with the backslash to ensure that each one is stored
as one character, rather than as an integer.
With the octal bother out of the way, we can give our attention to the graphics string
commands. The move and line commands each cause the cursor to move to a new position
which is ‘x’ points to the right of the old one, and ‘y’ points up. If you want to move left or
down, you will have to put a negative sign into the denary number, and then convert. The
program of Figure 9.3 will cope with negative numbers. Each number will be in two-byte
form, even if the number is a small one, and must be in octal, using the backslash. The only
absolute position command is 0, which means that the cursor moves to the origin, point 0,0,
the bottom left-hand corner. The m command can then be used to position the cursor, the
p command to position the cursor and light a point and the 1 command to draw a line. The
string of commands must end with a final 70’ so that the compiler can recognise the end.
Suppose we wanted to draw a square somewhere around the centre of the screen. Figure 9.4
shows the routine which would do this.
tfincluae stdio.h
ttaefine CLB i nl l ne (B;icd , iSxbbdb ) ;
mai n (>
\
char *stnng;
CLB;
strlng=“ 0 m\ 35 \ 1 \ 257\0 1 \ 0 \ 0 \ 62\0 1 \ 62 \ 0 \
0\0 1 \ 0 \ 0 \ 316\377 1 x 316 X 377 X 0 X 0 x 0 ";
draw(string);
rawx n ();
J
^include ?ba 5 ic 2 . 1 ib?
A square drawing routine.
FIGURE 9.4
9-5
The CLG command has been put in with a ^define, though it could just as easily have
been provided from the basic2.1ib, since this library has to be used in any case. If,
incidentally, you find that you get an error message while the stdio.h is being read, this is
always due to memory corruption and you will have to switch off and completely reload.
The string is the one that will draw the square. The starting zero puts the cursor to the
bottom left hand comer of the screen and the following four numbers of the m command
place it at the bottom left hand comer of the square position, which is x=285, y=T75 in
denary numbers. These translate into the octal set \ 35 \ 1 \ 257 \ 0. From this starting
point, the sides of the square are then drawn. The first step is to draw a vertical side by
specifying a y-change of 50 and an x-change of 0. This makes use of the octal set
\0\0\62\0, with the 1 command used for drawing the line this time, not just moving
the cursor. The next line is the top of the square, a movement of 50 steps in the positive y-
direction. This uses the octal sequence \62\0\0\0, and from this point, the
movements will need negative co-ordinates. The movement of -50 in the y direction is
programmed using octal \0\0\316 \ 377 , and the last movement of-50 in the x-
direction uses \ 316 \ 377 \ 0 \ 0, and the string ends with another \ 0. When this runs,
you’ll see the square drawn very quickly. The rawin() line ensures that the appearance of
the square is not disturbed by the ‘Type y to run:’ message until a key is pressed.
The use of a string of commands like this is rather restricting, because you can type only
about two lines of a string using the keyboard. You can, however, use a set of draw
statements in sequence, because each one can take over where the other left off. One
particularly useful way of doing this is to arrange the strings into arrays, and then use a loop
to draw the array strings.
tfmciude stdio.h
#detine CLG ini ine (0xcd ,0xbbdb) ;
mat n <)
t
i n t n;
char *siringL2D;
CLG;
stringC03 = '*0m\35\ 1 \257\0 1 \0\0\62\0 1 \62
\ 0 \ 0 \ 0 \ 0 “;
string! l 3 = "i \0\0\3i<b\377 1 \31<b\377\0\0\
0 “ ;
for (n=0;n<=l;n++)
draw(stringLn]);
rawin(>;
1
J
#inciude ?basic2.1 id?
How an array of draw strings can be used.
FIGURE 9.5
9-6
Figure 9.5 illustrates this with a simple example, the square pattern drawn as two sections
with an array of two pointers. This allows you to make as much use as you need of the draw
strings and also makes it possible to test drawings step by step, with one step assigned to
each pointer in a pointer array.
ttinciude stdio.h
idetine CLG inline(0xcd,0xbbdbi ;
matn t)
V
i nt n;
char *string[2Ji
draw ( “cb\0 ! ‘) ;
Cl_G;
stringC0d=“0m\35\l\257\0 cf\l 1\0\0\62\0
i\fa2\0\0\0\0“;
stringCi J-=“ct\3 i \0\0\316\377 \2i IV316
\377\0\0\0“;
for <n=0;n<=l;n++>
draw(stringtn]);
rawin();
T
J
#include ?basic2.1iD?
Adding colour instructions to a draw string.
FIGURE 9.6
Figure 9.6 illustrates this by using the separated strings to add colour instructions. The
colour command letter is c and it is followed by the letter \‘f or \‘b’, meaning
foreground colour or background colour respectively. These colours are taken from the
normal Amstrad INK range. For Mode 1, this consists offour colours using numbers 0 to 3.
You can, of course, change the colour set by using the ink() function in the basicl.lib set
of routines. The usual rules of PEN and PAPER colours are followed. If you want to change
the background colour you need to follow it with a CLG instruction, so that a separate
draw string is useful. In Figure 9.6 this has been done, using the conventional INK(0), dark
blue, as background, but with two different foreground colours. Note the syntax for the
colour changes, using cf \ 1 and cf \ 3.
More spectacular changes can be achieved by using the scale-changing features of
command s. Command s is followed by the usual four numbers, two for each dimension,
and it causes the scale of the drawing to be changed. The original scale is represented by a
factor of 4, so that using the string “s \ 4 \ 0 \ 10 \ 0” will make all of the x-dimensions
normal, as specified, and all of the y-dimensions double scale.
9-7
sinciuae stdio.h
#de+ine CLG inline(0xcd,0>;bbdb>;
main t >
int n;
char *stringC2D;
draw(“cb\0“);
CLG;
draw ( ,, s'\4\0\i0\0" ) ;
stringC0]="0m\35\1\127\0 c-f\i 1\0\0\62\0
1\62\0\0\0\0";
stringC1j=“cf\3 1\0\0\3i6\377 \2i i\316
\377\0\0\0";
tor (n=0;n<=i;n++)
drawlstringtn]);
rawin <);
-i
J
tinclude ?basic2.lib?
Using the scale-change command letter s.
FIGURE 9.7
In Figure 9.7, you can see that this causes the drawing to be stretched into a rectangle. Note
that the starting point has had to be altered to allow for the change in the y-dimensions.
The scale changing can be examined in more detail in Figure 9.8.
^include stdio.h
#detine CLG inline(0xcd,0xbbdb);
main t)
r
i nt n;
draw("cb\0");
CLG;
pattern ();
draw( ,, s\6\0\6\0\0 , ‘) ;
pattern () ;
draw ( “s\ 10\0\ 10\0\0‘‘) ;
pattern () ;
draw(“5\12\0\12\0\0") ;
pattern(>;
rawin () ;
9-8
#include ?basic2.1ib?
pattern(>
char * 5 tringl 2 ];
int n;
5tringti53 = “0 cfXl 1X0X0X62X0 1X62X0X0X0X
0 ■;
stringC1]="cfX3 1X0X0X316X377 \21 1\316
\377\0\0\0";
•for (n=0; n< = l; n++)
draw tstringLnl);
Illustrating the scale sizes, which are applied relative to the previous drawing.
FIGURE 9.8
In this example the drawing has been put into a function, and the scale strings are used in
the main. If you look at the sizes of the squares which are drawn in this routine, you’ll see
that they are by no means proportional to the (octal) numbers which follow the ratio
4:6:8:10 in denary. This is because each scale string operates on the previous string that was
drawn. For example, supposed that we used scales of 4,5,6 and 7 in sequence. If the square'
drawn with the scale of 4 (normal 1:1) had a side of 50 points, then the square drawn with
scale 5 would have 50*5/4=62 points (no fractions allowed). This, however, would be the
size of the square for the next action, so that the scale of 6 would produce a square of side
62*6/4=93 points, and so on. To prove this point, try making each scale number equal to 5
and you’ll see a group of squares drawn with equal increments in size. This is a point which
is not made clear in the manual, and only emerges when you study the routines in the
library that are used for the graphics functions. It’s a good illustration, in fact, of the action
of static integers in a function.
Finally in this section, the r command letter will cause a pattern to be drawn rotated by one
right angle in the clockwise direction. Once again, this is a command which has to be used
with some understanding. The effect of r is to rotate everything that follows it. If, for
example, you follow the r command with a move from 0,0 (bottom left hand comer) to the
centre of the screen, then the rotation will attempt to move from the corner to some point
off the screen. Any movements like this must be made before the r command is used.
♦include stdio.h
♦define CLS ini i ne(0xcd,0xbbdb);
main ()
int n;
draw t “cbX0" > ;
CLG;
draw(“0 5X4X0X10X0 (11X35X1X127X0X0");
pattern (>;
draw <“r X0");
pattern (> ;
draw("r X0“>;
pattern () ;
9-9
draw< ,i r\0‘' ) ;
pattern ();
rawin () ;
#include ?basic2.1ib?
pattern()
r
v
char *stringE23;
x nt n;
string[0] = "ct ‘\l i\0\0\62\0 1\62\0\0\0\0"
’ stringL 1 J = “cf \3 1 \0\0\316\377 \21 1\316
\377\0\0\0 a ;
■for (n=0; n< = i ; n++>
draw(stringEnH);
Illustrating the rotation of a drawing.
FIGURE 9.9
The program in Figure 9.9 shows the old square-drawing program altered to suit. The
scaling and the movement of the cursor are all done before the remainder of the routine is
called as a function. This shape is then rotated three times, forming a pattern of rectangles.
Notice that the rotation is always around the first point in the pattern, the one which
formed the bottom left hand comer of the original pattern.
Sounds unlimited.
In the space of this book a really full explanation of the sound commands of the Amstrad
machines is not appropriate, and I have to refer you to specialised books modestly naming
no names. The methods by which sound can be controlled using HiSoft C is, however,
rather different from the methods of Locomotive BASIC, and for that reason requires
rather more background information than would be needed otherwise. The Amstrad
BASIC SOUND commands require all of the instructions to be in number form. If you
read music, or can work with sheet music, this is the last thing that you want. The ideal
method of programming music would be to work with the named notes of music - and this
is what is done when you use the play() routine in the HiSoft C basicl.lib library. It
might appear to be the obvious thing to do, but very few computer languages do it!
If you have no experience of music, however, this may seem rather puzzling to you. How do
we go about writing down music? For each note we have to specify what the note is (its
pitch), how loud it must be and for how long it is to be played. In written music this is done
by using a type of chart for the pitch, and different shapes of markings (notes) for the
duration. Loudness is indicated by using letters such as f (loud) and p (soft). More than one
letter can be used, so that fff means very loud, and ppp means very soft. Each sound is
9-10
indicated by a note, a shape on the chart, and the shape of the note gives some information
about the duration of the note. In addition to this, each piece of music will start with some
advice about the speed at which the notes are to be played. One of these methods is a
metronome reading. The metronome is a gadget which ticks at regular intervals, and the
metronome reading for a piece of music is the number of metronome ticks per minute. A
more ancient way of indicating speed is the use of Italian words like allegro (fast), lento
(slow) and so on. What these speed settings decide is how many unit notes will be played in a
minute. The unit note is the crochet, so if a piece of music is marked at a metronome speed
of60 (pretty slow), then there will be 60 crochets played per minute. The durations of all the
other notes are decided in comparison to this unit, the crochet. A minim sounds for twice as
long as the crochet, a semibreve for twice as long as a minim which is four times as long as
the time of a crochet. The quaver sounds for only half the time of the crochet. A semiquaver
sounds for only half the time of a quaver, which is quarter of the time of a crochet. The
crochets and other timed notes are indicated by the shapes of the written notes, as Figure
9.10 shows.
Symbol
Time (relative
to crotchet)
Name
J*
Vs
Demisemiquaver
/
'A
Semiquaver
/
'A
Quaver
J
1
Crotchet
J
2
Minim
<>
4
Semibreve
How the shapes of the note symbols are used in written music.
FIGURE 9.10
In addition symbols are used to indicate silences in the music, and these are based on the
same idea of a unit duration of silence and others which are twice, four times, half, or
quarter. These silence marks are shown in Figure 9.11
9-11
Rest symbol
Time (relative to
crotchet)
t
%
1
'A
\
1
—
2
—
4
Silence marks in music.
FIGURE 9.11
The pitch of a note is indicated in written music by placing it on to a kind of musical map
which is called the ‘stave’(Figure 9.12).
Treble
Bass
The stave and how notes are placed on it.
FIGURE 9.12
Piano music uses two of these staves, each consisting of five lines and four spaces. The
upper stave is the treble stave, and it is used for writing the higher notes which will be
played on the piano with your right hand. The lower stave is the bass stave, the lower notes,
played with the left hand. Instruments which do not use a keyboard will normally have
music written with only one staff. In addition to this representation of notes by position on
staves, we also use the letters of the alphabet from A to G to name the notes.
The piano is the most familiar type of musical instrument, and its keyboard is set out so as
to make it very easy to play one particular series of notes called the ‘scale ofC Major’. The
scale starts on a note that is called Middle C and ends on a note that is also called C, but
which is the eighth note above Middle C. A group of eight notes like this is called an
‘octave’, so that the note you end with in this scale is the C which is one octave above
Middle C. Because music (in the Western hemisphere, at least) is based on this group of
eight notes, we use only the first seven letters of the alphabet in naming the notes. Why 7 ?
9-12
Well, the eighth note is the end of one octave and the start of the next, so it bears the same
name. The scientific basis of all this is that if you take Middle C and find the frequency of
the sound of this note, then the C which is the next octave above Middle C has precisely
double the frequency value of Middle C. The C below Middle C has half the frequency of
Middle C, and so on. That’s why the ancient Greeks always thought that music was a
branch of mathematics.
Arrangement of keys on a piano keyboard.
FIGURE 9.13
The appearance of these keys on the piano keyboard is illustrated in Figure 9.13. Middle C
is, logically enough, at the centre of the keyboard, and we move right for higher notes or left
for lower notes. One of the complications of music, however, is that the frequencies of the
notes of a scale are not evenly spaced out. The ‘normal’ full spacing is called a ‘tone’ and the
smaller spacing is called a ‘semitone’. Each scale will contain two semitones. On written
music, Middle C appears midway between the treble and bass staves.
The key instruction for playing music with HiSoft C is the play() function. Like draw(),
play has to be followed by a string name and a channel number. The string then contains
all the information that is needed to produce the music. The channel number is used in
exactly the same way as it is used in Locomotive BASIC, with numbers 1 to 7 for channels
and the higher numbers used for synchronisation, holding a channel, or clearing sound
buffers. The notes are specified simply by their names, as used in music. These are the
letters A to G, with upper-case letters used, and we also use the signs # and b. The # sign
means a semitone higher than the note indicated by the letter, so that A# is a semitone
above A - the note a musician would call ‘A-sharp’. Similarly, Ab would mean a semitone
below A, or ‘A-flat’. In addition to the letter names of the notes, we can use other control
letters to indicate the octave, volume, tempo, amplitude envelope, pitch envelope and
noise. The octave letter is (upper-case) O, and it has to be followed by a number whose
range is 0 to 8. If you don’t specify any ‘O’ value, the computer will set itself to 03, which is
the octave that contains Middle-C. The actual range of an octave for the purposes of the
play() function is from A to G, rather than the usual C to B range that is used with
computers. OO means the lowest range of computer notes; 08 gives the highest. This
means that the computer can play nine octaves of notes, which is more than the range of
any ordinary musical instrument. The volume control letter, V, can be followed by a
number whose range is 0 to 15. This lets us make music whose volume can change during
the playing of the music. As you might expect VO gives the lowest volume, V15 the greatest.
9-13
The computer sets to V12 if you don’t specify anything different. We can, of course, still set
the volume control on the computer itself to suit our own tastes. The duration of a note is
controlled by a number in the range 1 to 40 octal, and the default setting is 4. This duration
number, if needed, is placed following the note name. The duration of each note is mainly
set by the tempo of the music, and the number following the note will be used mainly to
change specific notes, not with each and every note. The tempo is set by using the T
command with a number in the range 1 to 377 octal.
Time now for some illustrations. This involves some extra work if you are using the
CPC464 model. Start by clearing out any program text, and then loading in the basicl.lib
text file. When you list this, then, at or near to line 4370 you’ll see the statement:
reg_bc=0x80FF; /* asynchronous, all RAM*/
which is intended for the later CPC664 (of the brieflife) and CPC6128 machines. If you are
using the CPC464, the statement has to be changed to 0x02FF to correct a problem in the
way that the CPC464 handles these routines.
We can then start with Figure 9.14.
♦include stdio.n
main ()
‘i.
setupsound();
pi ay <"T \74 0\3 C. D.E.F.G.A'.B'. C ' ." , 2) ;
J
♦include ?basici.iib?
A simple example of play in action. The function setup_sound must be called before any play action.
FIGURE 9.14
This calls the function setup_sound, which prepares the routines for handling the play
actions. The string is then defined and played in one action by using the play(string,
channel) form. The notes consist of the scale that starts at Middle C. How do I know?
Well, Middle C in the HiSoft C pattern is the third note in octave number 3, so by omitting
an octave command (which gives the effect of03) and specifying C, we get Middle-C. The
other notes have been written in sequence, but when we get to the note A, we are starting a
new octave. Unless you force the octave number higher, the A below C will be played. We
could use 04 for this purpose, but a more convenient method is to use the apostrophe sign.
The apostrophe following the note will raise the pitch of that note by one octave - you need
an apostrophe for each note that is altered in this way. Placing an apostrophe preceding a
note will lower its octave by one. The scale uses the default value of volume and a speed
(tempo) of 74 octal, 60 denary. This corresponds to 60 notes per minute, one per second,
which is a nice slow pace. This also is a default setting. Each note in the string is followed by
a full-stop, which marks the note as one to be played. The computer will ignore any note
which is not followed by this full-stop. When you run this the ‘Press y to run:’ message
appears almost at once, and if you press any key before the music has finished you will stop
the sound. Pressing the y key will always re-start the scale.
9-14
The scale of C is a simple one, but it’s a good piece of music to illustrate a few of the things
that can be done with this play command. Try Figure 9.15 now, to see what we can do with
the volume command, V, and the length numbers.
tinclude stdio.h
main ()
X.
char *music;
setup_sound();
mustc="T\170 V\2 C.D.E.F. V\7 G\62.A'.B
. V \ 17 C'\1.“;
play(music,2);
/
#include 7‘basicl.lib?
Using the V command letter to change volume, and using note duration numbers.
FIGURE 9.15
In the example, we have used the ordinary tempo, 120 denary (170 octal), but changed the
volume and length of note settings. The reason for having separate tempo and length
controls is that you can get the tune sounding right by using the length number to select the
length of notes which are not crochets, and then use T right at the start to set whatever
tempo you like. If you want to speed things up, use a low value for T, if you want a funeral
march, use a high value. You can even write the string without a T, and then add it in by an
earlier command like:
play(“T170”);
Another useful part of the string of notes is the W command letter. This means ‘wait’, and is
used to provide a pause between notes. The pause duration can be controlled in exactly the
same way as the duration of a note, by using an octal duration number in the range 1 to 40
following the W and separated by a backslash. The full stop must follow the W command
and its duration number just as if it were a note.
ffinciude stdio.h
main ()
cnar *mu.sicE2J;
l n t n;
set u.p sound () ;
Biusici0 j = ‘ , T\300 G.0\4 A. Bb. W\2. A. D. G.W\
9-15
musicC i 3 = 11 A. D. C. Bb \ 2 . A\2. Bb\2. C\2. Bb. vA4
. AMS.
for (n=B;n<=i;n++>
p1 ay(musicLnd,2);
Jf
♦include ?basici.lib?
More music, with the W command letter used for silences.
FIGURE 9.16
Figure 9.16 illustrates the use of the wait commands in a piece of music that also contains
flattened notes, using the ‘b’ sign. Unless you are a very experienced musician, you will
need to use sheet music to provide the notes for you. If you can read sheet music, it’s an easy
matter to translate it into the strings that are needed for the play function.
Using envelopes.
If you have used envelopes along with the BASIC of the Amstrad machines, you will not
find it too difficult to adapt to the use of envelopes with the play() function. If you use an
early model of the CPC464, however, you may not be aware from the manual of the variety
of envelopes that can be created. These are described in detail in a book of mine, Music &
Sound on the Amstrad CPC464. In this section, we’ll look very briefly at the methods by
which envelopes can be specified and used with the play function. The Amstrad manuals
concentrate on ‘software envelopes’, meaning that you have to design the shape of each
envelope for yourself. It’s never easy to do this, and one alternative is to make use of the set
of standard shapes which are built into the hardware. These are illustrated in Figure 9.17,
along with the values of reference numbers which produce them.
Number
Shape
Description
8
Fast up, slow down and repeat.
9
Fast up, slow down, then hold at zero
volume.
10
Fast up, slow down, then repeated slow up,
slow down.
9-16
15
Slow up, fast down and hold at zero volume.
The hardware envelope shapes of the Amstrad computers.
FIGURE 9.17
For producing a sound with a hardware envelope, then, you need to set up the number of
sections (1 to 5), and for each section the values of envelope number and envelope period.
Both the number of sections and the envelope number will be a single byte (a character
number), one in the range 1 to 5, the other in the range 8 to 15, and the period is an unsigned
number in the range 1 to 65535. These numbers can be set up in a form suitable for reading
by putting them into a structure. The function that creates the envelope can then be passed
the address of this structure, so that the numbers can be read in order.
#inciude stdio.h
struct hard {
char nr;
char shape;
unsigned period;
> section;
main <)
9-17
int j,n,k;
char w;
unsigned x;
setup_sound ();
section.nr=i;
tor (w=8;w<=15;w++)
r
pri nt-f < " \n"/.s 7.d" , “Envelope No. 11 ,w) ;
x = l;
for(j = 1;j <=4;j ++)
x *— 10;
pr i ntt ( " \n7.s 7.d “ , "Per l od " , x ) ;
section.shape=w+12B;
/♦must have bit 7 set*/
section.period=x;
S_amp1_envelope(1,§ion);
p1 ay <“Y\1 C.“,2 >;
tor(k=0;k<=10000;k++) ;
*v
j
jf
•»
j
ttinclude T’basic 1.1 ib?
#include ?stdio.lib?
An illustration of how to set up hardware envelopes, and how they sound.
FIGURE 9.18
Figure 9.18 illustrates how hardware envelopes are set-up, and how they sound. Various
values of period are used for the full range of envelope numbers, and the screen prints up
the values as you hear the sounds. Several points of detail are important. One is that the
envelope must be numbered, both in the call to S_amp!_envelope and in Y command
of the play string. The principle of the envelope statement is that the number of sections is
set up, and each section requires three bytes of data. For a hardware envelope, each of thse
sections consists of a shape number and a two-byte period. The shape number consists of
the envelope shape code, number 8 to 15 inclusive, with 128 added to it. The addition of 128
is vital, because this sets bit 7 of the byte, and is the way that the machine-code routines of
the Amstrad machines recognise the difference between a hardware envelope and a
software one. There is no need to set duration or volume to zero, as you have to when you
use the BASIC sound statements with an envelope. If you do, in fact, the program will hang
up with no sound and no loop running, but it can be recovered by using the (ESC) key. The
duration number in the play string will control the overall time for which the note is
sounded.
9-18
The program runs slowly because of the time delays that are built in. The time delays are
essential, however. The reason is that the sound generator is a little computer in its own
right, and if you issue it with a play() function it gets on with it independently. If you do not
use a time delay in this program then you can find the screen displaying values of envelope
and period which are well ahead of what the loudspeaker is playing. This is because the
display is fast, but the music has been forced to play at a slower pace. This can be very
useful, because it means that if you mix music with other computing actions, you will not be
held up while the music plays. You will find that the play function ofHiSoft C allows many
more notes to be put into a queue than the SOUND command ofLocomotive BASIC. You
will find, as you run Figure 9.18, that several of the envelope values sound pretty much the
same. There are in theory only eight different wave shapes, and not all of these can be easily
distinguished unless you have a good ear for sound. In particular, if you use short values of
period, you will not hear the effect of the ‘repeater’ notes that you get with envelope number
values of 8,10,12 and 14. You will find, in fact, that for a lot of notes you can hear only a few
main types.
If you haven’t used hardware envelopes from BASIC, you might be confused by the ‘period
number’. The name suggests that its value might set the total time of each envelope. In fact,
it sets the time for each step when the waveform amplitude is changing. If you consider
envelope shape 13 as an example, it consists of the sloping part, the ramp and a steady part.
The ramp consists of sixteen steps of amplitude (from 0 to 15), and the period number setfc
the time between each step. The Amstrad hardware manual states that the step time is 128
microseconds. My own timing indicates that this may be a misprint, and that the time per
step should be 12.8 microseconds, corresponding roughly to a period number of 5000 for a
step time of one second. Step times as long as this are useful only for special effects. Another
point to note as regards timing is that when you use machine code, the firmware places a
two-second delay following any hardware envelope. If you want to use hardware envelopes
for music then each envelope should use its hardware section(s), followed by a software
section of silence which will then set the timing for the overall envelope. The timing in ‘C’,
however, is still controlled by the duration number which is part of the note specification.
#iDelude stdio.h
typede-f struct!
char shape;
unsigned period;
} hard;
struct i
char sect;
hard sectionC23;
}soundlt;
max n ()
setup_sound (>;
soundit.sect=2;
soundi t. sectlon CBd.shape=l37;
soundi t. sect i on ES3] . peri od=lB£ZS0;
9-19
soundit.sectionL13.5hape=l;
sound!t.section L i J.period=10240;
S ampl envel ope (1 , Stsoundi t) ;
pi ay t" Y\1 C\1.D\1.E\1.F\1.B\1.A'\1.B'\1.
C'\i.“,2);
J
#include Vbasici.lib?
ttinclude ?stdio.lib?
Mixing a hardware section and a software section.
FIGURE 9.19
Figure 9.19 shows how such an envelope can be constructed, using one hardware section
and one silence section. A more elaborate structure is needed this time because we must
have one structure to contain the number of sections and the section description array, then
another structure to contain the parts of each section. The illustration in the manual (under
the description of S_ampl_envelope) shows how to allow for a mixture of hardware
and software envelopes by using a union, a topic that will be mentioned in Chapter 10. In
this simpler example, both types of sections have been put into one structure. The structure
whose type is hard is defined in the usual way as having one char for the shape number,
and an unsigned for the period, a total of three bytes. The main structure, soundit,
consists of a char sect which will hold the count number, and an array section of type
hard which will hold the section structures. The main program then calls setup_sound()
as usual, and assigns values to the sections. The BASIC equivalent of what we want to do is
the two section envelope which would be programmed with ENV 1,=9,1000,1,0,40 and
the ‘C’ version starts with assigning soundit.sect, the number of sections as 2. The
numbers for the sections are then allocated. For the hardware part the numbers 9+128 and
1000 are assigned as you might expect, but for the software section things'are not quite so
straightforward. The software section which in BASIC uses numbers 1,0,40 will still use
the 1 as its first number, but the other two have to be written in unsigned form. This means
multiplying the third number by 256 and adding it to the second. Since 40*256+0=10240,
that’s the number we put in as the ‘period’ number. We are still using three bytes, but in a
different way. The hardware deals correctly with this set of three because the first number
is less than 128, signifying a software section. The address of the structure then has to be
passed to S_ampl_envelope, and everything is ready to play. The envelope will not
give correct timing, however, until the duration of each note is put to its minimum figure of
\ 1. This, in fact, exercises more control on the envelope duration than the software
portion, and makes it much easier to use hardware envelopes.
What about using software envelopes, then? The method is no more difficult than we have
looked at so far. If you are using an envelope which is purely a software one then the
structure is slightly changed, as Figure 9.20 illustrates.
9-20
♦include stdio.h
typedef structC
char count,si 2 e,time;
} so-f t;
struct t
char sect;
soft sectionC3 3;
>soundit;
rna 1 n ()
f
L
setup sound(>;
soundit.sect=3;
soundit.section[0].count=l;
soundit.section C0J.size=8;
soundlt.sectionl0J.time=2;
soundit.sectiontID.count=5;
sound i t.section C1J.size=255;
soundit.sectionC1 3 •time=4;
soundit.section £2 J.count =2;
soundit.section L23 .size=251;
soundit.sectionC23.time=2;
S_ampi envei ope (1, Stsoundi t > ;
pi ay ( " Y\ 1 C\ 1. D\ 1. E\ l.FU. G\ l.A'\l.B'\l.
C \1.“,2);
i
♦include Pbasici.lib?
♦include ?stdio.lib?
A software envelope, and the structure which is needed to define it.
FIGURE 9.20
The three bytes in each section of a software envelope represent, in order, the step count,
step size and pause time. Each of these can be a single byte character, so that structure soft
has now been defined as containing three characters. This involves one slight complication
- the use of negative numbers of step size. The number that has to be used to represent a
negative step size is 256-step_size, so that a step size of-1 is put in as 255, and a step size
of-5 is put in as 251. The example then shows a three-section envelope being set up and
used. This particular envelope has a curious echo effect that will sound quite different with
different lengths of notes - try it out for yourself!
This brings us to the end of this section, with a huge variety of sound effects unexplored. We
haven’t looked at noise, nor at multichannel music and synchronisation. The reason, apart
from lack of space, is that all of these features are programmed in much the same way as in
BASIC. If you have a sound program which runs in BASIC, then the library functions in
basicl.lib will allow you to program the same sounds in ‘C\ The HiSoft Manual is
particularly helpful to you in this respect, by showing the ‘C’ library equivalents of the
sound statements.
9-21
Chapter 10
Assortment.
In a book of this size, it’s impossible to cover all the possibilities that a language like ‘C’
offers, and I have aimed at dealing with the essential features that most users of HiSoft C on
the Amstrad will need. I have concentrated, in particular, on the aspects of ‘C’ which a
programmer who has previously used BASIC will find difficult. Nevertheless, we have
covered most of the important topics in reasonable detail, and the main thing that you will
need from now on is practice. Only a lot of practice, particularly in planning programs, will
release you from the frustration of finding errors reported each time you compile, and then
getting a different set of errors when you try to run your program. In this chapter we shall
look at some points that have been skipped over previously, or which need more emphasis.
The most important of these topics is fault finding.
Inevitably, when you start to learn a new language you are going to make a lot of errors in
syntax. This is particularly true if you have previously programmed only in BASIC. The
most common mistakes are omitting semicolons, and forgetting the brackets and the
inverted commas in printf statements. These errors are found by the compiler and
reported in such a way that they should not be difficult to remedy. There are several such
syntax errors, however, which produce reports that don’t lead you to the fault unless you
have had some experience. If, for example, you revert to BASIC habits and write an array
member with round brackets instead of square brackets, you can get some very interesting
error reports. For example, if you have the line:
str(j)=s[j];
then you can get the report:
ERROR 40
Can only call functions
which will certainly draw your attention to the line that causes the problem, but doesn’t
necessarily remind you of what the fault is. The problem is that round brackets mean a
function to the compiler, so using str(j) makes it appear that you are trying to assign a
value to a function, which is impossible.
10-1
Another fruitful source of error messages lies in assignments. You become so accustomed
to the few data types in BASIC, that you assume that almost any variable can be equated to
any other. In ‘C’ simple data types are treated quite loosely, and you can play with ints and
chars almost as if they were interchangeable. This shouldn’t be allowed to go to your head,
though, because there is an important difference. The char type needs only one byte of
memory, whereas the integer needs two. The importance of this in reading the EOF
character has already been mentioned. The other side of the coin is that ‘C’ treats strings
(arrays of characters) rather more strictly than BASIC does. In particular, you cannot
assign a string to a variable name by any simple method such as using stringl=string2 as
you might in a more BASIC-like language, or even in Pascal. Instead, strings must be
assigned character by character, as strcpy or bit does, or by passing pointers. This is
probably one of the items that causes most annoyance to ex-BASIC programmers! In
addition, even the most obvious error messages may not be all that they seem. An error
message delivered in the middle of a line may refer to the preceding line, or even to a line
which is several steps away. The most baffling message is usually the Missing type,
because whatever is reported as missing may be staring you in the face, on the screen. As the
manual comments, this particular message is a shorthand form for several types of faults
and you simply have to look at your program listing rather closely.
To anyone coming to ‘C’ from BASIC, the use of the semicolon to mark the end of a
statement is sometimes baffling because of the exceptions. The most important rules
concerning semicolons are that there should be none following while, do , or following the
loop statement such as:
for (j=0;j<=100;j++)
unless this is being used purely as a time delay. You don’t need semicolons following
/’remark*/ lines, but if you do put in the semicolons the compiler will not object. The
program will object, however, if you have put in a semicolon following a # define. As the
manual points out, using #deflne NULL 0 means that NULL is defined as 0 throughout
the program. If you use #define NULL 0; then NULL is defined as 0; which is not a
number, and which will cause some very peculiar things to happen. The problem here is
that the error is not found at the time when the ^define line is being dealt with, but later,
when you attempt to use NULL. The error message will also be one that does not make it
clear what has happened, like ‘Missing )’. The worst errors occur when you have done
something instinctively. Top among this type of thing is using x=y when you mean x==y
in a test. The statement if (x=y) .... means that the value of y is assigned to x and if it is not
zero then something has to be done. The statement if (x==y)... means that something is
to be done only if x is identical to y. The confusion arises because both statements are legal
syntax, but with very different meanings.
Other errors are rather easier to spot because they involve actions which you would not be
working with in BASIC. Pointers are a notorious source of problems, even for fairly
experienced programmers in ‘C’. One very common cause of trouble is declaring a pointer,
but forgetting to assign some value to it before using it. A less obvious problem is that a
string name is a pointer to the string, but an integer or character name is not. In addition,
the name of a structure needs the & sign if it is being passed to a function. The other
common pointer error is to forget that incrementing or decrementing a pointer will
10-2
automatically cause a change of the correct number ofbytes, according to the variable type.
The scanf function is another potent source of errors. It’s always wise to start a scanf
control string with a blank, because this prevents problems when the input is done in a loop.
By using a blank, scanf will skip over any (RETURN)/(ENTER) character which is in the
keyboard buffer from a previous entry. At various stages in this book, we have seen what
problems this stored character can be when other input functions are used. One minor
point about scanf is that there is no %u specifier string for unsigned numbers. Using “%u”
in a scanf input will cause very odd corruption of the other variables. A more common
problem is to forget that each variable in scanf must be a pointer, so that string names or &
with other names will be required.
Even when a program compiles correctly, there is no guarantee that the program will run
without fault. An error-free compilation means only that the statements in the program are
of correct syntax and do not contain any undeclared variables or impossible assignments.
You can compile without errors, for example, a program that will try to put more items into
an array than the array has space for, or which has a faulty switch statement in which you
can enter a reply for which no case is provided. In general, run-time errors are caused by
faulty planning of one sort or another rather than by faulty typing or bad use of statements.
You may have omitted to test the size of an entered quantity, for example, or given no
thought to how many items could be put into an array. If you are exerienced in
programming with any other language, then the run-time errors should present no
problems to you.
Hints & tips.
There are a few odd hints and tips which can make your ‘C’ programming career rather
easier. Don’t forget, for example, the very useful find-and-replace facilities of the HiSoft C
editor. As always, if you have programmed only the Amstrad in BASIC you will not have
come across these useful aids to programming. If you have used any good word-processor
software, however, you will know how much time can be saved by using search and replace
actions. In a program which consists of a large number of printf statements, for example,
you can save a lot of typing time by abbreviating printf to P. You can make similar
economies with scanf and gets. If you use a lot of these search and replace actions, though,
it’s a good idea to keep a note of which abbreviation you have used for which purpose. If you
don’t there is a risk of using the same abbreviation for two different instructions, or of
forgetting that you have used an abbreviation. These editing actions are particularly useful,
of course, in versions of‘C’ which do not use line numbers, but if you do not have a printer
it’s very handy to be able to find, for example, which line contains something like /*FIND
DATA*/. In addition to the use of the editor, of course, you can make use of ^define. You
can, for example, use a fine such as:
#define P printf
to make each P in the program act as a printf. This is less desirable than using the editor,
however, particularly if you intend to print out your program at any stage. The reason is
that it makes the ‘C’ statements look non-standard, and so much more difficult to read. It’s
better to keep the use of #define for quantities such as NULL, EOF and the like, which
can vary from one system to another but which should be reasonably standardised in
programs.
10-3
Other actions
Inevitably, when you make use of a language some parts of that language will come in a lot
more use than others. In the course of this book, I have tried to put the greatest emphasis on
the features of‘C’ that you are likely to spend most time with. This emphasis has meant
that other features have been omitted or lightly glossed over, and in this section we’ll try to
remedy that deficiency. It’s possible that none of the actions which are described here may
ever interest you. On the other hand, one or two of them might just be exactly what you
have always needed but were afraid to ask about. Particularly belonging to this section are
the statements and operators that apply to binary numbers and to machine code. You will
find that many of the functions in the basic libraries are written by using calls to the
firmware. If you are a proficient machine-code programmer you may wish to write
different functions for some applications, depending on your interests. You might, for
example, want to add a circle(pos,rad) function, or a sound function which is more
similar in syntax to the BASIC version. All of this is possible, and it makes ‘C’ a particularly
useful language which is never static. HiSoft have promised to release new libraries as more
functions are added, and this is a very desirable method of supporting the language. In
addition, the C User Group in the U.S.A. keeps discs full of source-code routines, though
these are not at the moment available in the unusual Amstrad 3" disc format. If you can
make use of larger discs, however, it’s possible that you could read the ASCII text from
these discs, which consist of ‘public-domain’ software. Public domain means that no
copyright is involved, and the programs are free though you need to pay for discs, copying
time, postage etc. It would help if these programs could be made available as listings, but
unfortunately they are,not.
We’ll start with a keyword that you may have noticed in the list, but which we haven’t used.
This takes very little time, because entry is simply a spare word which has no effect on any
compiler at present. It is a word for an action which is not implemented, and might never
be. It should not be typed into a HiSoft C program, as the compiler will reject it. A much
more important topic concerns coercions and cast. As you will have seen, ‘C’ treats
characters and numbers in a fairly interchangeable but well-defined way. This means that
the result of an expression which uses mixtures of items will be obtained by using a set of
rules. These rules are summarised in Figure 10.1, and Figure 10.2 illustrates them in
action.
1. Type char is always converted into type int.
2. If an unsigned number is used in an expression, the result is always unsigned.
3. If the numbers are integers, the result is also integer.
4. Any change to this scheme can be made only by using cast.
The rules for coercion of number types.
FIGURE 10.1
10-4
main < >
£
char c;
int j ;
unsigned p;
c= ' a ';
j=50;
pr 1 nt f ( “ \n7.d " , j+c) ;
p=36;
p+=50+c;
printf ( "\n/£u“ ,p) ;
■fc
J
A program which illustrates coercion in action.
FIGURE 10.2
Three variable types are declared, and arithmetic is carried out. The addition of a character
to an integer produces an integer result, the sum of the integer value and the ASCII value of
the character. The sum of the unsigned number, integer and character produces an
unsigned number because the character converts to integer, and the sum of an integer and
an unsigned is always unsigned. Assignments, however, will convert to whatever form is
called for if possible. For example, if you declare int j and char d and then assign 65 to j,
then d=j can be performed and will result in the character d printing as an A, ASCII 65.
These coercions are completely automatic, but there are times when we want to make
coercions that are not automatic. We may, for example want to make a pointer into an
unsigned number or an unsigned number into a pointer. This latter action is particularly
useful if we want to perform the equivalent of PEEK or POKE, as the library illustrates.
For such actions, the cast action is provided. Note that the use of the word cast is a feature
of HiSoft C, and does not appear in all other versions. In many varieties of‘C’ the cast
operation is carried out by using the syntax of cast (with brackets), but without the word
‘cast’ being used. This makes it only too easy to perform a cast action unwittingly, and the
specific use of cast is a useful enhancement to the language. The syntax is of the form:
j=cast(type)variable
in which the brackets enclose the type that you want the number to be cast to.
ma ini)
’i.
char *p;
unsigned j;
*p= d ;
prints ( “ \nV.c :i , *p ) ;
j=cast tunsigned)p;
pr l nt+ t “ \n>.u" , j ) ;
J
Illustrating the use of cast.
FIGURE 10.3
10-5
Figure 10.3 illustrates the use of cast in forcing a pointer to be cast to an unsigned number.
This is not likely to be needed because you want to perform arithmetic on pointers, since
you can perform most of the arithmetic actions that you need to on pointers in any case. It’s
much more likely that you need to assign a definite value to a pointer.
More complications.
The data types that are supplied by ‘C’ are fixed, and you are not allowed to make up your
own data types as you are in Pascal. You can, however, assign names of your own to types
by using typedef.
nidi n (/
i
i nt j;
typedef int *p_int;
p_int point;
J=45;
poi nt=& j;
printf ( " \n%d‘‘ , *point) ;
Using typedef to declare a pointer type.
FIGURE 10.4
Figure 10.4 shows a typical example. The line:
typedef int *p_int;
means that we can now use p_int as if it were a variable type like int or char. It is, in fact,
a pointer to an integer, and by declaring p_int point we make the word point a variable
of type pointer to integer. We can therefore assign to point a pointer value, the address at
which the value of j is held, and we can print the value held in this address by printing
♦point. This action of typedef can be very useful, but if too many types are defined it can
make a program difficult to follow.
Typedef does not create new types but there is one type that we haven’t considered so far,
the union. A union is a ‘hold-anything’ variable, one that can be used to hold a character,
integer, string or whatever we like. It sounds marvellous, but in fact it’s not used as much as
you might expect. A union has to be declared in very much the same way as a structure is
declared, using a pattern of the form shown in Figure 10.5.
10-6
union bo^sl
char c;
char
int j,k;
> chief;
main ()
t.
chief.c= A ;
printf ( “ \n7.c“ ,chief . c) ;
chief.s=“STRING";
pr 1 ntf ( " \n’/.s“ , chi ef . s) ;
Creating a union, a form of variable that will hold any one of a set of types.
FIGURE 10.5
In this example, the type union is declared with the pattern name of boss. The union can
contain a character, a pointer, or an integer named j or k. Note that this is one or another. A
structure, by contrast, contains all of the types that are specified, the union contains any
one. In the lines that follow, a type is assigned and its value then printed out. The important
point is that you can assign only one value at a time, and you must select the correct name,
such as chief.c , chief.j or whatever is needed. When a union variable is declared, it will
reserve as much space as is needed for the largest of its contents. If you make a union type,
for example, which contains a character, an integer and a four-character string, then the
string is the longest member and will make the union four bytes long, assuming that the
total string length is four. A structure would need one byte for the character, two for the
integer, and four for the string, a total of seven bytes. If you attempt to print out data which
has not been assigned to a union, you will get garbage. For example, if you have assigned a
string to pointer s in the union, then trying to print out a character chief.c or an integer
chief.) will produce results which may be useful, but usually are not. In this particular
example, the attempt to print chief.) will usually produce the pointer address chief.s.
Finally in this particular collection of less-used statements, we come to the least-used of
all, goto. Having to use goto in a ‘C’ program is the programmers equivalent of driving a
car with a sign on the back that says ‘Take care, wally driving’. The use of GOTO in BASIC
has little enough justification, because Locomotive BASIC has the WHILE...WEND loop.
In ‘C’, the use of goto is useful mainly when you want to try out a loop by a piece of quick
editing. Once you have proved the point, you would normally want to make a more
permanent form of loop using while , do or for. Of course, once your program has been
compiled into machine code with #translate no-one will ever know. The use of goto
requires a label name, which is used to mark the start of the loop or the place to which goto
leads to. Unlike BASIC, which uses the line number as a label, ‘C’ requires a word to be put
in and followed with a colon, as the illustation in Figure 10.6 shows.
10-7
[Ticsin U
char *5;
1 n t j ;
s=‘*I am a wally...'*;
__cr _
J —w*?
here:j—;
/♦start of loop*/
pr 1 ntf < " Xn/.s" , s) ;
it (_i== 0 ) goto there;
goto here;
there:;
Using goto, with its label word.
FIGURE 10.6
The colon can be folowed by a statement, or simply by a semicolon. The effect of the goto
will be to make the program move to the point which is labelled. The provision of the break
and continue statements in loops provide for practically all the uses that might otherwise
justify the use of goto, and for that reason it’s seldom used.
The # commands.
We have made intensive use of two § commands, ^include and ^define, in programs so
far. These commands are often called ‘pre-processor’ commands, because of the way that
‘C’ is implemented in other computers. In these machines there is a separate piece of
program which deals with these commands, using them to alter the text of a program. For
example, if ^define NULL 0 has been used at the start of a program, the pre-processor will
replace each NULL by a zero throughout the program. Only after all this has been done
will the compiler get to grips with its work. Similarly, if you have included other files by
using commands such as #include strcmp , then the pre-processor will get these files
(assuming there are such files on the disc) and place their text into yours. In HiSoft C there
is no separate pre-processor, and all of this work is done by the main compiler. HiSoft C
also allows a few # commands which do not feature in the pre-processor syntax of most ‘C’
compilers. One of these is #error. When you type #error into your fisting, you release a
considerable chunk of extra memory in the computer. This was the memory that was
formerly used for the detailed error messages, as distinct from the ERROR number
messages. The use of terror is therefore a useful method of squeezing in a very long
program, but the messages will remain suppressed until you switch off and re-load HiSoft
C. Unless you can find where the messages are stored and how their presence is indicated,
there isn’t much choice about this.
10-8
The #direct command works in a quite different way. To use #direct, you must first have
typed c and (RETURN)/(ENTER). This puts the compiler in action, and typing #direct+
then puts it into direct mode. This means that any statement which you type will be
executed when you press (RETURN)/(ENTER). You must be careful not to press
(RETURN)/(ENTER) until you have finished typing statements, which means that you
simply type statements separated by semicolons, not in lines. This is most useful for items
that are self-contained, like rawout(4);rawout(l);, rather than for extended statements.
It’s handy if you want to send codes to a printer, or to make use of some peeks and pokes or
rawout statements. You can get back to normal compiler use by typing #direct-, using
(ESC) - or by making a mistake in a statement that is to be executed directly.
The other common # command is #list, which once again is a feature of HiSoft C. Using
#list- suppresses listing while a program is compiling, and using #list+ restores the
listing. You can see this action at work when you make use of the routines from the
libraries. It speeds up compiling to some extent because the screen print routines of the
Amstrad machines are rather slow, and in any case once a program is debugged you don’t
paticularly want to see it listing each time it compiles. It’s a good idea, then, on your longer
programs to incorporate a title using /‘remark*/ lines, and suppress listing immediately
after this until the end of the program. That way the heading shows what program is
compiling, but the actual listing does not appear.
Statics in functions.
The HiSoft C manual encourages you to make use of static variables wherever possible,
because of the saving in memory. This is not the advice that you get with other versions of
‘C’ but since the Amstrad is, by comparison with the machines on which ‘C’ was
developed, of modest memory size, the advice is sound. Because static is advised as the
normal memory type, it’s easy to forget precisely why it was evolved and how it can be used.
Figure 10.7 is a reminder.
main<)
r
V
i nt j ;
for ( j = l; j<=S$ j +•+)
stadem();
1
J
stadem()
r
i.
static ln t k = l;
pnntt < “ \n 7 .s '/.d “ , “ k is " , k) ;
k++;
J
A simple program to illustrate the action of a static variable in a function which is called several times.
FIGURE 10.7
10-9
In this example, a function is called five times. In the function, the static variable k is
initialised to 1, and its value is printed, then incremented. When the program runs,
however, you will see the incremented values of k appear in a count-up. The effect of
declaring k as static and initialising it to 1, is to make it have this value of 1 the first time the
function is called. At the end of the function, however, the value of k is preserved - this is
what a static variable is all about. When the function is called again the initialisation step is
ignored, and the value of k which is stored from the previous time is used. The value ofk
cannot be used in the main program, because it does not exist in the main program. Even if
you have a variable called k in the main program, it will have no effect on the k that is used
in the function. This aspect of a static variable is very important if you want tp make use of a
function in which a variable is changed. You can avoid the change if you carry out a
separate assignment. If, for example, you follow the declaration of k with the line:
k=l;
then the printout from the program will show that k has always been 1. For many purposes,
however, the automatic storage of variables can be very convenient. In general, programs
written in ‘C’ for other computers are likely to use static variables only where this type of
action is wanted, and you may have to be careful if you write these programs again for
HiSoft C, using mainly static variables.
Another action which is sometimes needed is a variadic function. The functions that we
normally use have a known and fixed number of arguments, like action(a,b,c,d). We
sometimes need, however, to pass a variable number of arguments. Many textbooks show
this applied to main() itself, but this is not something that you can do with HiSoft C. The
header stdio.h shows this action used for two functions, max and min, which are
designed to find the maximum or minimum value, respectively, in a list of numbers. The
argument of each function, then, will consist of as many items as there are numbers. The
essential feature of a function of this type is to follow its header name with the word auto.
This indicates to the compiler that the function will be of this ‘variadic’ type, and that the
number of arguments is variable. The output of the auto actions will be to supply two
special arguments for the function which are called, by convention, argc and argv. The
function itself is written with one argument in the brackets. This argument, unlike any we
have met before, doesn’t have to be declared anywhere because it is a special quantity, the
number of bytes of argument which have been used when the function was called. Suppose,
for example, that you were dealing with integers. Since two bytes are used for each integer,
ten numbers in an argument would require 20 bytes. By convention an extra pair ofbytes is
always held available, making 22. This is because the use of argc and argv in HiSoft C is
not quite the same as its use in other versions. In many versions of‘C’, argc and argv are
used to pass commands, with arguments, directly to main() so that you can call upon a
compiled main program by using its name and passing some values. This is not possible
when the program has to be compiled each time it is used. In the original method, argc is
the number of arguments, and argv is a pointer to an array of strings, one string per
argument. In this method, argv[0] is the program name itself, forming one of the strings,
so that a program with no arguments still has an argv[0], and its argc value is 1. The first
real argument is argv[l], making argc equal to 2 and so on. On this basis, argc always
contains one item more than the total number of items. If the arguments are integers, then
this extra argc value corresponds to two bytes.
10-10
total(number) auto
r
static int argc,*argv,t;
argc=number/2-l;
argv=&nuiTiber+argc;
whi1e(argc —)L
t+=*argv;
—argv;>
return t;
J
ma in()
r
L
int j ;
j=total(1,6,4,8,6,2);
pr i nt-f ( “ \nXd " , j ) ;
>
A variadic function, in which the number of arguments is variable.
FIGURE 10.8
In the program of Figure 10.8, then, we start by defining a variadic function called total.
The program must place this function before any call that is made to it, even though it is, in
this case, a function that returns an integer. The header of the function contains only the
argument number which will be the number of bytes in the argument when the function is
called. This quantity number does not have to be declared. Instead, we declare the integers
argc and t, and the integer pointer argv. Because number consists of one item more than
the actual list of arguments, we find the item-count from number/2-1. The pointer argv
then has to be set. The arguments are stored in memory addresses which decrement, so that
the address that we get from &number when we read this quantity is the lowest address at
which the last argument is stored. To get to the first argument, at the top end of the memory
range, we need to add argc, the item count. The items can then be dealt with in a loop that
decrements argc and argv together. In this example, the argument numbers are added to
the totalling integer t, so that the total can be returned. If you want to see what is going on,
add some lines that will print values of number, argc , argv and *argv at intervals.
Binary and machine code.
For many purposes, binary numbers and machine code are unnecessary and undesirable.
After all, the whole point in i venting higher level languages like ‘C’ was to avoid the chore of
programming in machine code or assembly language, and having to think in terms ofbinary
numbers. Nevertheless, there are times when we have to do just that, either in order to save
memory, devise some very clever programming or speed up the execution of something.
The standard ‘C’ language provides several operators for use with binary numbers, and the
HiSoft version also provides a new reserved word, inline, which has the effect of entering
machine-code routines directly into memory. The provision of inline, incidentally, makes
it comparatively easy to write a Z-80 assembler for yourself. That’s just the type of task that
‘C’ was intended to make easy.
10-11
We’ll start with the operators that carry out binary logic comparisons of two numbers. The
three binary operators of this type are & (AND), I (OR) and A (XOR). Note that these are
single characters, and you must be very careful to distinguish the & from &&, and the I
from I I , because these are used in very different ways. If you aren’t familiar with binary
logic operators this is no place to start learning, and I suggest that you skip the rest of this
chapter until you have had time to digest a book on binary numbers and machine code.
main ()
T.
char c,d;
c=8xA6;
d=8x3C;
printf ( " \n'/.5 7.x“,‘‘fiND gives " ,c&d);
printf (“\n7.s %x , ‘,"OR gives " ,cid);
printf <“\n7.s 7.x “ , “XOR gives “ , cd) ;
Using some of the binary operators. These are the three operators which make a logical comparison of
binary bits.
FIGURE 10.9
Figure 10.9 illustrates the binary operators being used to compare two numbers. The
numbers are written in hex code, using the Ox prefix which is used to mark hex numbers in
HiSoft C. A number written normally is always taken to be a denary (ordinary scale of ten)
number unless it is being used along with a backslash. Numbers which are preceded with 0,
or as characters with a backslash, are taken to be in octal code. Because of this use of the
zero, you must be careful not to start any ordinary denary number with a zero. In this
example, the numbers are hex and are prefaced with the Ox. The numbers are declared as
characters which means that we are working with eight bits only, and the three printf lines
give the results of AND, OR and XOR operations carried out bit by bit. The printf lines
make use of the %x specifier to print out the results in hex. In case you are dubious about
these results, Figure 10.10 shows an analysis of what is happening.
Hex A6= 10100110 in binary
Hex 3C= 00111100 in binary
AND gives 00100100 which is hex 24.
Hex A6= 10100110
Hex 3C= 00111100
OR gives 10111110 which is hex BE.
Hex A6= 10100110
Hex 3C= 00111100
XOR gives 10011010 which is hex 9A.
An analysis of the binary number actions.
FIGURE 10.10
10-12
HiSoft C does not permit some of the ‘bit field’ actions of the ‘C’ standard, but the
equivalent effects can all be obtained by suitable use of these operators. For example, if you
wanted to check that bit 4 in a byte was set, you could use a test like:
if (c&&0xl0==0xl0).
and you can make use of the usual ‘masking’ effects of numbers such as OxFO and OxOF.
These bitwise operations can be used, of course, on integer numbers as well. The integer is
stored as two bytes, so that the results of these operations will be expressed as two byte
numbers unless they can be fitted into a smaller space. Remember that small negative
denary numbers (like -5) will be stored as a two-byte integer, with the upper byte equal to
OxFF, so that bitwise comparisons with such numbers will often result in four-figure hex
numbers.
In addition to these operators which compare two numbers, there are two shift operators
which operate on a single number, integer or character. The left shift is indicated by «,
which must be followed by a number that gives the number of places shifted. Using <<1
will cause a left shift of one place, using < <3 will cause a left shift of three places. These left
shifts are logical shifts, with zeros being used to fill in the right hand side. The shift will
change the most significant bit of the upper byte if necessary, so that the apparent sign of an
integer can be changed by a shift of an integer. The right shift is signalled by using >>,
again followed by a number. This is an ‘arithmetic’ type of shift because the most
significant bit of the upper byte is not shifted, and the shift action copies this byte at the left
hand side rather than putting in zeros. In other words, if the integer starts with the bits 10..,
thenaright shift ofone place will make this 11.. rather than the 01 which would result from
a logic shift. The right-shift action therefore does not change the sign of a number.
Some shift actions are illustrated in the program of Figure 10.11.
main (>
char c , d;
c=0xA6;
d=0x3C;
printf
(
" \n7.s
7.x'
V‘sh
> ;
printf
i
" \n7.s
/lx '
V'sh
l) ;
printf
/
V
“ \n7.s
7.x 1
' , "sh
ces- “
d<<3) ;
printf
<
" \n'/.s
7.x ‘
‘ , " sh
aces-
(i
, d > >3)
5
ift left c- “,c << 1
ift rignt c- ",c »
ift left d three pla
ift right d three pi
The use of shift actions on binary numbers.
FIGURE 10.11
10-13
The two numbers are declared as characters and one is shifted by one place left, then by one
place right. The other number is shifted in each direction by three places. In each case the
results are printed in hex, and you can see the effects of type coercion working to display
two of the results as integers rather than characters. F igure 10.12 shows an analysis of these
shifts.
Hex A6 is 10100110 in binary
When this is shifted one place left, with zero added on the right hand side, it
becomes:
101001100 which is hex 14C.
Hex A6 is 10100110 in binary
When this is shifted one place right, it becomes:
01010011 which is hex 53.
Note that the left hand bit has changed - the rule about the left hand bit not
changing applies only to 16 bit numbers.
Hex 3C is 00111100 in binary
When this is left shifted by three places, it becomes:
000111100000 which is hex 1E0.
Hex 3C is 00111100 in binary
When this is right shifted by three places, it becomes:
00000111 which is hex 07.
10-14
Sixteen-bit shifts.
Hex C03C is 1100000000111100 in binary.
When this is left shifted by three places, it gives:
0000000111100000 which is hex 01E0.
Hex C03C is 1100000000111100 in binary.
When this is right shifted by three places, the most significant digit of 1 is
retained, and copied into the shifted places. This gives:
1111100000000111 which is hex F807.
Analysing the effect of shifts.
FIGURE 10.12
If you now modify the program to declare int d and assign the number 0xC03C to d, you
can run the program again to see the effect of left and right shifts on a number which has its
most significant bit set. The left shift behaves as a normal logic shift, giving OxOlEO, and
changing the sign of the number if it is printed as a denary number. The right shift copies
the most significant bit of‘I’ into the the next three places, and gives the result 0xF807 of
the same sign as the original number. These are also shown analysed in Figure 10.12. You
will find the shifts widely used in the library routines as a method of multiplying or dividing
by 2, or for bitwise analysis.
Inserting machine-code.
The inline reserved word of HiSoft C is not a standard ‘C’ word. It is, however, a very
useful enhancement of standard ‘C’, because it allows you to write library functions which
include, or are made exclusively of, machine code. Since all of the facilities of the computer
are available from machine code, this allows you to write, for example, a circle(centre,
radius) function or new methods of controlling the sound system as you please.
Obviously, it helps considerably if you know how to write and use machine code, but even if
you are not an expert with machine code it can be useful. You can, for example, make use of
small pieces of machine code which you see printed in magazines, and transform them into
‘C’ library functions. You need to know what you are doing, and the code must be the type
that is ‘relocatable’ meaning that it can be written in any part of the memory. The inline
statement does not allow you to choose memory addresses for your machine-code, it
simply places it ‘in fine’ with the rest of the code that the ‘C’ compiler generates, hence the
name.
10-15
main < >
f i 11 < > ;
J
f i i i < >
r
i nl ine(
0x UD , 0x , 0x BB
0X01,0xE7,0x03
0x3E,0x41
0x CD,0x5A,0x BB
0x0B,0x 7B,0xB1
0x20,0xF6
);
Using inline to insert machine code into a function. No RET code must be used.
FIGURE 10.13
Figure 10.13 shows a very simple example of inline in use. The machine code program is a
trivial one - it simply fills the screen with the letter ‘A’. The important point is how it is
introduced into the ‘C’ program, and how it is terminated. Normally, a machine code
program ends with the RET command, 0xC9, or with some variation on this command.
This should not be done when inline is used, because the RET will cause a lockup unless it
is part of a subroutine within the code. Y ou have to be careful about some looping programs
in machine code which might include conditional returns such as RET Z. The best way of
adapting these is to change the conditional return into a jump to the last byte of the
program, which can then be a NOP byte of 00. No such complications arise in this example,
however, and the code is written following the inline word, with the code enclosed in
brackets. Note that the code can be written as shown in separate lines, with commas
separating the code bytes. No semicolon must be used until the final bracket has closed on
the codes. The compiler will then assemble the code as it comes along, and place it as part of
the compiled ‘C’ code. In this example, when you run the program, the screen fills with the
letter ‘A’, and the final line then scrolls to allow the ‘Type y to run:’ message to appear.
unsigned _hl;
main ()
V
i nt n, j;
for <n=0jn<=iB0;n+=2>
*v
side (n> ;
for <j = l;j< = l300;j ++) ;
si de (n )
10-16
_hi=n;
inline C
8;-; , -S<_h 1 ,
Bx uD, 0 k 8b>, 8x BC
> ;
Passing a parameter to a machine code routine which is written inline. The routine performs a sideways
scroll, using the position number which is passed from the main program.
FIGURE 10.14
Figure 10.14 shows an advance on this technique. This time a parameter is passed to the
machine-code routine, using the method which is normal for HiSoft C. Unlike the BASIC
method this depends on the use of a variable as a temporary store, and this variable, hi, is
declared as unsigned before the start of the main program. The machine code itself is a
sideways scroll routine, which requires a position number into the HL register pair, then a
call to 0xBC05. The important point here, then, is the method of getting the parameter
value n into the HL register pair. Normally when you interface from BASIC to machine
code, the first part of your machine code program consists of reading the parameter bytes
using the IX registers. The HiSoft manual points out that the IX register is also used when a
function is called, but the passing of the parameter must be done in a different way. The
method is to pass the parameter to the unsigned number_hi. Obviously, you could call
this whatever you like and the basicl.lib routines use reg_hi, but whatever you use it’s a
good idea to have a name that reminds you of what it’s to be used for. The number stored in
this variable then has to be passed to the routine and, in this case, it’s needed in the HL
register pair before the shift routine is called. The transfer is done by using the code for LD
HL, which is 0x2A, and following this inline with & _hi to get the address of the variable.
The call to 0xBC05 is then made in the next fine of codes and, as usual, the set of inline
codes ends with no RET byte. If, incidentally, you require an inline function to return an
integer or pointer, this can be done by loading the required bytes into the HL and BC
registers before the routine ends.
This concludes our tour through the facilities of HiSoft C. Like any other language ‘C’
takes some time to learn, and a lot of practice is needed to become really familiar with it. It
also requires you, like any other language, to write programs of your own design to become
really familiar with the feel of the language. In my pogramming time I have had to use
Fortran, Basic, Pascal and C, and of these ‘C’ is the one that is always the greatest joy to
return to and write in. It’s difficult to say why, but I think it’s best summed up in the word
‘fascination’. ‘C’ is a fascinating language to use, not least because almost every action that
can be carried out with the hardware you have can be programmed with ‘C’. The other side
of the coin is the lack of safeguards - that a ‘C’ program can suffer from an obscure bug
which may keep you up all night trying to swot. To me, that’s part of the fascination, the
pitting of wits against the inexorable logic of the machine and the language. I shall never
tire of it, and I hope that I have been able to pass some of my own sense offascination to you.
10-17
Appendix A
Binary, Octal and Hex codes.
Throughout the history of computing, programmers have used various forms of number
codes in preference to the normal scale of ten. The reason is basically that computers store
numbers in memory units, each of which is a type of switch. A switch can be either on or off,
and so each unit of a memory can store only two codes, one for on and one for off. We can
put this into number terms by using off to mean 0 and on to mean 1. With only these digits
to use, then, all numbers have to be stored and manipulated inside the machine using
binary code, which has only the two digits 0 and 1. Using only two digits makes no great
difference to the way that we write numbers, however. In denary (scale of ten) we count up
from 0 to 9, and then the next number is written by placing a T in another column, the
‘tens’ columns, and a zero in the units column. In binary the count in the units column is
from 0 to 1, and then the next number is 10, - one ‘two’ and no units. The number three is
then represented as 11, a two and a unit, and four needs another column, 100. Figure A.l
illustrates the sequence of these numbers, and shows how to convert between binary and
denary numbers.
Position values -
Bit.No.
7 6 5 4 3 2 1 0
Value
128 64 32 16 8 4 2 1
For each further place to the left, use a position number which is double the previous one.
Binary count from 0 to 15 denary.
Denary
Binary
Denary
Binary
0
0000
8
1000
1
0001
9
1001
2
0010
10
1010
3
0011
11
1011
4
0100
12
1100
5
0101
13
1101
6
0110
14
1110
7
0111
15
mi
A-l
Conversions:
1. Denary to binary.
Divide the number by 2, and put the remainder next to it. Do the same with the result of the
division, and so on until the last number is zero. Then read the remainders from the bottom
up.
Example: Conversion of 58 denary.
Number - 58 divide by 2=29 and 0 over.
29 divide by 2=14 and 1 over.
14 divide by 2=7 and 0 over.
7 divide by 2=3 and 1 over.
3 divide by 2=1 and 1 over.
1 divide by 2=0 and 1 over.
Reading from the bottom of remainders gives 111010, which is the binary equivalent. This
can be padded out with zeros on the left hand side to make it an 8-bit or a sixteen-bit
number. For example, as an 8-bit number, it would be 00111010.
2. Binary to denary.
Write the binary number with the position values above the T digits, then add the position
values. For example, the binary number 01011011 is written as:
64
16
8
2
1
1 1
1
1
1
1
1
0 1
0
1
1
0
1
1
The position values added give 64+16+8+2+1=91.
Binary numbers, and conversion between binary and denary.
FIGURE A.1
The trouble with binary numbers is that they contain a lot of l’s and 0’s, quite a dazzling
number if your computer happens to use them in groups of 32 or more. Small computers
use only eight binary digits (or bits) at a time, but even so, the sets of l’s and 0’s can
hypnotise you into making silly mistakes. Because of this programmers have devised other
code systems, of which the most common are octal and hex. For microcomputers octal is
very seldom used, and its use in HiSoft C is the first I have ever encountered on a micro.
A-2
The trouble with denary, you see, is that ten is not a power of two, as 4,8, and 16 are. This
makes it much more difficult to convert easily between denary and binary. Octal is based on
a scale of eight, and its main feature is that conversion between octal and binary is very
simple, as Figure A.2 shows.
Octal count 0-7 as in denary, then-
Denary
Octal
8
10
9
11
10
12
16
20
etc.
Conversion of denary to octal:
As for denary to binary, but dividing by eight. For example, denary 467 is converted as
follows:
467 divide by 8=58 and 3 over.
58 divide by 8=7 and 2 over.
7 divide by 8=0 and 7 over.
This makes the octal number equal to 723.
Octal to denary.
As for binary, but use the place numbers:
32768 4096 512 64 8 1
For example, the octal number 421 is 4*64 + 2*8 + 1= 273 denary.
Octal and Binary.
Each octal number corresponds to three bits of binary, from 000 to 111. See the binary
number table for these equivalents. To convert octal to binary, simply write down the
three-bit binary equivalent of each octal digit. For example, octal 254 becomes:
A-3
2 5 4
010 101 100
giving the binary number 010101100.
For converting from binary to octal, divide the binary number into three-bit groups,
starting from the right hand side. Convert each group into octal, including any one or two
bit number at the left hand side.
For example, the binary number:
1001110110100110 is grouped as:
1 001 110 110 100 110
giving
1 1 6 6 4 6 octal.
Octal numbers, and their relationship to binary.
FIGURE A.2
The digits of an octal scale are simply the digits 0 to 7 with the new culumn being used for
each power of 8, such as 8, 64, 512 etc.
Hex, or hexadecimal, is even better from the programming point of view. One single hex
digit will represent a number which uses up to four binary digits. This makes the system
particularly suitable for modern microcomputers, which use groups of 8, 16, and 32 bits
almost exclusively. Since a scale of sixteen is used, we need digits for 0 up to denary 15, and
for the numbers denary ten to denary fifteen we use the letters A to F, as Figure A.3 shows.
A4
Denary
Hex
0
00
1
01
etc.
to..
9
09
10
0A
11
0B
12
OC
13
0D
14
0E
15
OF
16
10
17
11
etc.
The hexadecimal (hex) code.
FIGURE A.3
The conversions between hex and binary are particularly simple, as Figure A.4 illustrates.
Hex
Binary
Hex
Binary
00
0000
91
1000
mm
0001
mm
1001
0010
0A
1010
sai
0011
0B
1011
04
0100
OC
1100
05
0101
0D
1101
06
0110
0E
1110
07
0111
OF
mi
To convert hex to binary, write the equivalent four-bit binary number for each hex digit.
For example, to convert Hex AF to binary, write down the codes 1010 for A and 1111 for F
giving 10101111 as the binary equivalent.
To convert from binary to hex, group the binary digits into fours starting from the right
hand side. Then write the corresponding hex digits, not forgetting any one, two or three
digit group at the left hand side.
For example: Binary 11001011011 is grouped as 110 0101 1011, so that in hex it is 6 5 B.
Converting between hex and binary.
FIGURE A.4
A-5
Finally in this brief summary, we come to the problem of negative numbers. There is no
provision for the use of a negative sign in binary, octal or hex. The system that is used is to
make the most significant bit (left hand bit) of a binary number act as a sign bit. If we use
16-bit numbers, for example, like the int type of‘C’, then a number 0111111111111111 is
positive, and the number 1000000000000000 is negative. Figure A.5 shows how the binary
equivalent of negative numbers can be found, and how the conversion in the other direction
is achieved.
For integer numbers of two bytes, the left hand bit (bit 15) is used as a sign bit. This means
that the number 0x7FFF is the largest positive integer, denary 32767. The number 0x8000
is equivalent to -32768, the largest negative number. In denary terms, to find the
equivalent of a negative number subtract the number value from 65536, and then convert
to octal, hex or binary. When converting back, a negative result should be converted in the
same way.
For example, converting-76 gives 65536-76=65460, which in hex is FFB4, and in octal is
177664. In binary, this is 1111111110110100.
Converting the binary number 1100111101101011 into hex gives CF6B, octal 147553, and
denary 53099, so that the number it represents is -12437.
Negative numbers in binary code.
FIGURE A.5
A 6
INDEX
A
arrays 4—4
arithmetic heirarchy 2-9
argument 3-2
assignment 1-2
atoi() 2-16,3-5
automatic variables 2-14
compiler 1-1
compound statement 3-11
concatenation 8-14
continue 3-12, 5-6
constants 2-11
curly brackets 2-6
B
D
backslash 2-8
backup 2-1
bad declaration 2-6
bar (I ) 1-7, 5-4
basicl.lib 9-8
basic2.1ib 9-2
beep 3-2
binary 10-11, apx A
break 3-12,5-1
byte 4-1
c
d command 1-5
data, structured type 4-4
decimal numbers 2-9
declaration of variables 1-1, 2-14, 7-16
default 5-2
^define 2-12
deleting lines 1-5
denary numbers 2-9
disc commands 1-7, 5-8
division 3-7
do... while 4-10
draw 9-3
c command 2-6
case 5-1
cast 7-7, 10-4
cent 8-8
centring text 8-8
changing strings globally 1-5
chat variables 2-16
clear screen 2-7
coercion 10-4
comments 2-6
compiling programs 2-6, 2-10
E
editing 1-4, 2-7
else 3-9
end of statement (;) 2-2
enter key, ignoring 5-4
entry 10-4
envelope, sound 9-16
exchange 7—4
expecting a primary here 2-7
extern variables 2-15, 3-15
(ix)
F
f command 1-5
false 3-9
fault-finding 10-1 et seq.
fclose 5-9
fgets() 5-14
field 4—6
fielding 2-13
filing commands 1-5, 1-7
filit() 6-7
finding strings 1-5, 8-16, 8-18
floating point 2-11
fopen 5-9
formatting 2-6 et seq.
form feed 2-8
fprintf 5-9
fscanf 5-9
function 1-3, 3-1 et seq.
G
g command 2-1
G_clear_window 9-2
G_line_absolute 9-2
G_move_absolute 9-2
G_set_pen 9-2
getc() 5-10
getchar() 2-15
getting files or data 2-1, 5-9
global find/replace 1-5
goto 10-7
graphics 9-1 et seq.
H
hexadecimal apx A
I
i command 1-4, 2-7
identifiers 2-A
if test 3-9
#include 1-6, 2-6
selective 3-3
indexed variables 4—4
initialisation of variables 2-14
ink 9-8
input
disc/tape 2-1, 5-9
keyboard 2-15
tape/disc 2-1, 5-9
instr 8-16, 8-18
int 1-2
integer 1-2
division 3-7
interpreted language 1-1
isalpha 5-6
isspace 5-6
J
justification 2-9
K
keywords 2-2 et seq.
keyboard input 2-15
keyhitQ 5-11
L
1 command 2-6
left$ 8-9
left justification 2-9
library functions 1-1, 3-3
line numbers 1-3 et seq.
linked lists 7-14
#list 3-5
listing programs 1-6, 2-6
fists, finked 7-14
loading 2-1, 5-9
local variables 2-14
logical or 5-4
loop 1-1
M
machine code 1-6, 10-11
main function 2-2
mathematical heirarchy 2-9
maths symbols 3-8
max 3-5
mid$ 8-9
min 3-5
missing error 10-2
mode (screen) 2-17, 3-3
N
n command 1-5
names 2-4
new line 2-9
not operator (!) 3-8, 5-8
number specifiers 2-9
o
octal code 2-8, 9-3, apx A
operators 3-8
or (I ! ) 5-4
output
disc/tape 1-5, 1-6, 5-8
printer 1-6
screen 1-6
tape/disc 1-5, 1-6, 5-8
P
p command 1-5
Pardon? error message 1-5
pass 1-1
percent sign 2-9, 3-7
play 9-10
pointers 4-1 et seq., 5-9
poke 3-3
portable code 1-6, 2-4
predefined identifiers
pre-definition 1-3
preprocessor 2-12
printat 8-5
printf identifier 2-6
printouts 1-6
putc() 5-10
putchar() 4-10
putting files or data on tape or disc
1-6, 5-8
Q
qsort 7-11
queue ,sound 9-19
R
rand 9-2
random numbers 9-2
rawinQ 5-7
rawout() 2-16
reading files or data 5-9
records 6-1
recording data 5-8
register variables 2-15
remarks 2-6
renumbering fines 1-5
reserved words 2-5
restriction 2-15
return 3-13
rightf 8-9
right justification 2-9
running a program 2-6
s
S_ampl_envelope 9-18
saving to disc or tape 1-5, 1-6, 5-8
scanf 4-3
screen
modes 2-17, 3-3
output 2-6
selective inclusion 3-3
semicolon (;) 2-2
separators 2-2
setup_sound 9-14
Shell-Metzner sort 6-19
sorting 6-16
sound 3-2, 9-10 et seq.
source code 1-5
srand 9-2
statement 2-2
static 1-2,2-14
stdio 3-3
strcat 8-14
strcmp 5-7
strcpy 4-9
string 1-3, 8-1 et seq.
string^ 8-12
strlen 8-4
strncpy 6-15, 8-4
strpbrk 8-18
strspn 8-19
structure 1-3
structured data types 4—4
structured variables 4—4
subscripted variables 4-4
symbols 3-8
switch 5-1
T
tab 8-2, 8-3
tape commands 1-7, 5-8
test() 3-9
tolower 5-6
Too many operators 6-19
translate 1-6,2-10
true 3-9
type command 2-1
typedef 7-16, 10-6
types of variables
automatic 2-14
char 2-16
extern 2-15, 3-15
indexed 4—4
integer 1-2, 2-15
local 2-14
nested structures 6-26
pointer 4-1 et seq., 7-1 et seq.
records 6-1 et seq.
register 2-15
static 1-2, 2-14
string 1-3, 8-1 et seq.
structure 6-1 et seq., 6-26
structured 4-4
subscripted 4—4
u
undefined symbol 1-6, 2-2
undefined variable 2-14
V
variables 2—4, 2-14
automatic 2-14
char 2-16
extern 2-15, 3-15
indexed 4—4
integer 1-2,2-15
local 2-14
nested structures 6-26
pointer 4-1 et seq., 7-1 et seq.
records 6-1 et seq.
register 2-15
static 1-2, 2-14
string 1-3, 8-1 et seq.
structure 6-1 et seq., 6-26
structured 4-4
subscripted 4—4
vertical space 8-8
vspc 8-8
w
w command 1-6
while 4-10
write 7-3
(xii)
Programming in ‘C’
on the Amstrad
The language ‘C’ has been used for a considerable
time by professional programmers using minicom¬
puters, but not until the arrival of Hisoft’s ‘C’ has it
been available on Amstrad microcomputers.
This book assumes that the reader is familiar with
Locomotive BASIC and frequently compares this with
‘C’. However, ‘C’ has much more to offer than BASIC
and many things which are quite difficult to do in
BASIC can be quite simple in ‘C’. The author has taken
every opportunity to show the reader the new ways of
programming available and has included many
reminders about old BASIC habits that must be
abandoned.
Because of the close inter-relationship between the
way programs are designed and the way they are
written, the author links these topics together. The
book has been written entirely around the conven¬
tional ‘top-down’ method of structured programming,
a method designed to make complex programs rela¬
tively easy to write and understand.
This book is suitable for users of the Amstrad CPC464,
CPC664, and the CPC6128.
GLENTOP
PUBLISHERS ■ LIMITED
Glentop Publishers Ltd.,
Standfast House, Bath Place,
High Street, Barnet, Herts. EN5 1ED
Tel: 01-441 4130
ISBN 0-1D7712-flb-3
£ 8.95
9 78
9
7 792
B64
o
[FRA] Ce document a ete preserve numeriquement a des fins educatives et d'etudes, et non commerciales.
[ENG] This document has been digitally preserved for educational and study purposes, not for commercial purposes.
[ESP] Este documento se ha conservado digitalmente con fines educativos y de estudio, no con fines comerciales.